From a31e3c483ae1fc688e9cdc511ca72c765ad9e2ba Mon Sep 17 00:00:00 2001 From: Lancy Norbert Fernandes Date: Fri, 15 Sep 2023 10:03:02 +1200 Subject: [PATCH] =?UTF-8?q?11.2.1=20-=20iOS=201=EF=B8=8F=E2=83=A37?= =?UTF-8?q?=EF=B8=8F=E2=83=A3=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .swiftlint.yml | 8 +- MEGA.xcodeproj/project.pbxproj | 859 ++- .../xcshareddata/xcschemes/MEGA.xcscheme | 14 + .../xcschemes/SearchDemo.xcscheme | 77 + .../Account/AccountRepository.swift | 6 +- .../AudioVideo/AudioSessionRepository.swift | 4 +- .../Repository/Calls/CallRepository.swift | 45 + .../Calls/MeetingCreatingRepository.swift | 177 +- MEGAData/Repository/Chat/ChatRepository.swift | 7 + .../Chat/ExportChatMessagesRepository.swift | 3 +- .../Chat/ManageChatHistoryRepository.swift | 73 - .../Chat/ScheduledMeetingRepository.swift | 2 +- .../Files/FilesSearchRepository.swift | 2 +- .../Node/NodeActionRepository.swift | 288 - .../Photos/PhotoLibraryRepository.swift | 6 +- .../Transfer/DownloadFileRepository.swift | 44 +- .../User/UserInviteRepository.swift | 14 - .../SDK/Extensions/MEGANode+Additions.swift | 1 + .../SDK/Extensions/MEGANode+FilePath.swift | 1 - .../Chat/Call/CallEntity+Mapper.swift | 25 +- .../Chat/Call/WaitingRoomEntity+Mapper.swift | 28 + .../Chat/ChatListItemEntity+Mapper.swift | 1 - .../User/UserAttributeEntity+Mapper.swift | 39 - .../InviteRequestDelegate.swift | 36 - .../MEGAResultMappingRequestDelegate.swift | 27 - .../Mocks/MEGAPurchase/MockMEGAPurchase.swift | 1 - .../AccountPlanPurchaseRepositoryTests.swift | 16 +- .../Repos/DownloadFileRepositoryTests.swift | 56 + .../Repos/NodeActionRepositoryTests.swift | 2 +- .../Extensions/MEGANode+Additions_Tests.swift | 1 - .../Entity/Call/EndCallDialogType.swift | 1 + .../MeetingCreatingRepositoryProtocol.swift | 1 + ...eetingNoUserJoinedRepositoryProtocol.swift | 1 - .../UseCase/Calls/CallLocalVideoUseCase.swift | 4 +- MEGADomain/UseCase/Calls/CallUseCase.swift | 35 + .../Calls/MeetingCreatingUseCase.swift | 5 + .../UseCase/User/UserImageUseCase.swift | 19 +- .../Album/AlbumCellViewModelTests.swift | 152 +- .../Album/AlbumContentViewModelTests.swift | 495 +- .../Album/AlbumListViewModelTests.swift | 70 +- .../Album/AlbumNameValidatorTests.swift | 1 + .../Links/ImportAlbumViewModelTests.swift | 266 +- .../TestHelpers/MockAnalyticsTracker.swift | 8 +- .../Mocks/MockAudioPlayerHandler.swift | 5 +- .../Routers/MockAudioPlaylistViewRouter.swift | 5 + ...ayerViewRouterNodeActionAdapterTests.swift | 95 + .../AudioPlayerViewRouterTests.swift | 122 +- .../ViewModel/AudioPlayerViewModelTests.swift | 1 + ...meraUploadConcurrentCountCalculatorTests.m | 1 - .../PhotoAlbumContainerViewModelTests.swift | 9 + .../Chat/ActiveCallViewModelTests.swift | 1 + .../Chat/ChatContentViewModelTests.swift | 1 + .../Chat/ChatRoomsListViewModelTests.swift | 13 +- ...nforceCopyrightWarningViewModelTests.swift | 1 + .../GetLinkAlbumInfoCellViewModelTests.swift | 1 + .../GetLink/GetAlbumLinkViewModelTests.swift | 84 +- .../GetLink/GetAlbumsLinkViewModelTests.swift | 81 +- .../HomeSearchResultsProvidingTests.swift | 102 + .../DiskFullBlockingViewModelTests.swift | 1 + MEGAUnitTests/MEGA.xctestplan | 24 +- .../Meeting/ChatRoomViewModelTests.swift | 1 + .../FutureMeetingRoomViewModelTests.swift | 5 +- ...MeetingParticpiantInfoViewModelTests.swift | 61 +- .../Meeting/Mocks/MockCallUseCase.swift | 56 +- .../Meeting/Mocks/MockChatRoomUseCase.swift | 5 + .../Mocks/MockMeetingCreatingUseCase.swift | 28 +- .../Meeting/Mocks/MockUserInviteUseCase.swift | 10 - .../WaitingRoomViewModel+Additions.swift | 27 +- ...etingCreationIntervalFooterNoteTests.swift | 1 + ...onMonthlyCustomOptionsViewModelTests.swift | 1 + ...MeetingSelectedFrequencyDetailsTests.swift | 1 + .../ScheduleMeetingViewModelTests.swift | 1 + .../Meeting/WaitingRoomViewModelTests.swift | 254 +- .../Mocks/MockTransferWidgetResponder.swift | 26 + .../NameCollision/ActionViewModelTests.swift | 1 + .../NameCollision/HeaderViewModelTests.swift | 1 + .../AlertModel+Equatable.swift | 0 .../CustomModalModel+Equatable.swift | 0 .../PermissionAlertRouterTests.swift | 0 .../PermissionAlertModelTests.swift | 40 + .../UpgradeAccountPlanViewModelTests.swift | 62 +- ...odel_createAccountPlanViewModelTests.swift | 12 +- .../AddPhoneNumberViewModelTests.swift | 1 + .../SMSVerificationViewModelTests.swift | 1 + .../VerificationCodeViewModelTests.swift | 1 + .../CookieSettingsViewModelTests.swift | 2 +- .../Settings/FeatureFlagViewModelTests.swift | 1 - .../SlideShowOptionViewModelTests.swift | 1 + .../TextEditor/TextEditorViewModelTests.swift | 1 + .../CancellableTransferViewModelTests.swift | 1 - .../AlbumNameUseCase+AdditionsTests.swift | 1 + .../TurnOnNotificationsViewModelTests.swift | 1 + .../Warning/WarningViewModelTests.swift | 1 + .../QuickAccessWidgetManagerTests.swift | 48 + .../MEGAChatSDK/Sources/MEGAChatSDK | 2 +- Modules/DataSource/MEGASDK/Sources/MEGASDK | 2 +- .../Domain/MEGAAnalyticsDomain/Package.swift | 8 +- Modules/Domain/MEGADomain/Package.swift | 8 +- .../Entity/ABTest/ABTestFlagName.swift | 1 - .../Account/AchievementTypeEntity.swift | 1 - .../Account/SMS/CheckSMSErrorEntity.swift | 1 - .../Account/SMS/GetSMSErrorEntity.swift | 1 - .../Entity/Account/SMS/RegionEntity.swift | 1 - .../Entity/Account/SMS/SMSStateEntity.swift | 1 - .../MEGADomain/Entity/Ads/AdsFlagEntity.swift | 9 + .../MEGADomain/Entity/Ads/AdsSlotEntity.swift | 6 + .../MEGADomain/Entity/Album/AlbumEntity.swift | 20 +- .../Entity/Album/AlbumMetaDataEntity.swift | 11 + .../AccountPlanAnalyticsEventEntity.swift | 1 - .../AnalyticsEventEntity.swift | 1 - .../DownloadAnalyticsEventEntity.swift | 1 - .../ExtensionsAnalyticsEventEntity.swift | 1 - .../MediaDiscoveryAnalyticsEventEntity.swift | 1 - .../MeetingsAnalyticsEventEntity.swift | 1 - .../NSEAnalyticsEventEntity.swift | 1 - .../Entity/Banner/BannerEntity.swift | 24 + .../Entity/Banner}/BannerErrorEntity.swift | 8 +- .../MEGADomain/Entity/Call/CallEntity.swift | 21 +- .../Entity/Call/ChatRoomPrivilegeEntity.swift | 1 - .../Entity/Call/ChatSessionEntity.swift | 1 - .../Entity/Call/NetworkQuality.swift | 1 - .../Call/ScheduledMeetingErrorEntity.swift | 1 - .../Entity/Call/StartCallEntity.swift | 1 - .../Entity/Call/WaitingRoomEntity.swift | 13 + .../Entity/Chat/ChatConnectionStatus.swift | 1 - .../Entity/Chat/ChatContainsMetaEntity.swift | 1 - .../Entity/Chat/ChatGeolocationEntity.swift | 1 - .../Entity/Chat/ChatGiphyEntity.swift | 1 - .../MEGADomain/Entity/Chat/ChatIdEntity.swift | 1 - .../Chat/ChatMessageEndCallReasonEntity.swift | 1 - ...hatMessageScheduledMeetingChangeType.swift | 1 - .../Entity/Chat/ChatMessageStatusEntity.swift | 1 - .../Entity/Chat/ChatRichPreviewEntity.swift | 1 - .../Entity/Chat/ChatRoomDelegateEntity.swift | 1 - .../Entity/Chat/ChatRoomErrorEntity.swift | 1 - .../Entity/Chat/ChatSourceEntity.swift | 1 - .../Entity/Chat/ChatStatusEntity.swift | 1 - .../Entity/Chat/ChatTypeEntity.swift | 1 - .../Chat/ManageChatHistoryErrorEntity.swift | 1 - .../ScheduleMeetingErrorEntity.swift | 1 - .../ContextMenu/ContextMenuActions.swift | 1 - .../DeviceCenter/BackupSubstateEntity.swift | 1 - .../NameCollisionActionType.swift | 1 - .../NameCollision/NameCollisionType.swift | 1 - .../Entity/Node/CopyOrMoveErrorEntity.swift | 1 - .../Entity/Node/ExportFileErrorEntity.swift | 1 - .../Node/NodeFavouriteErrorEntity.swift | 1 - .../Node/SaveMediaToPhotosErrorEntity.swift | 1 - .../Entity/Plan/AccountPlanEntity.swift | 11 +- .../Entity/Plan/AccountPlanErrorEntity.swift | 1 - .../Entity/Plan/AccountPlanTagEntity.swift | 1 - .../Entity/Plan/AccountPlanTermEntity.swift | 4 - .../Requests/AccountRequestEntity.swift | 1 - .../Settings/APIEnvironmentEntity.swift | 1 - .../Settings/AppConfigurationEntity.swift | 1 - .../Settings/CookieSettingsErrorEntity.swift | 1 - .../Entity/Settings/Help/FeedbackEntity.swift | 1 - .../Entity/Share/ShareErrorEntity.swift | 1 - .../Transfer/FolderTransferUpdateEntity.swift | 1 - .../Entity/User/ContactLinkEntity.swift | 1 - .../User/ContactRequestStatusEntity.swift | 1 - .../Entity/User/InviteErrorEntity.swift | 1 - .../Entity/User/UserAttributeEntity.swift | 1 - .../User}/UserImageLoadErrorEntity.swift | 3 +- .../Versions/FileVersionErrorEntity.swift | 1 - .../Entity/Video/CodecIdEntity.swift | 1 - .../Entity/Video/ShortFormatEntity.swift | 1 - .../ABTest/ABTestinRepositoryProtocol.swift | 1 - .../Account/PSARepositoryProtocol.swift | 1 - .../Account/SMS/SMSRepositoryProtocol.swift | 1 - .../Ads/AdsRepositoryProtocol.swift | 6 + .../AudioSessionRepositoryProtocol.swift | 1 - .../CaptureDeviceRepositoryProtocol.swift | 2 +- .../Banner/BannerRepositoryProtocol.swift | 10 + .../Call/CallRepositoryProtocol.swift | 7 + .../Chat/ChatRepositoryProtocol.swift | 1 + .../ManageHistoryRepositoryProtocol.swift | 1 - .../Chat/WaitingRoomRepositoryProtocol.swift | 5 + .../Contact/ContactsRepositoryProtocol.swift | 4 +- .../CreateContextMenuRepositoryProtocol.swift | 1 - .../FeatureFlagRepositoryProtocol.swift | 1 - .../MEGAHandleRepositoryProtocol.swift | 1 - .../NetworkMonitorRepositoryProtocol.swift | 1 - .../Node/ImportNodeRepositoryProtocol.swift | 1 - .../Node/NodeActionRepositoryProtocol.swift | 2 +- ...odeFavouriteActionRepositoryProtocol.swift | 2 +- .../Node/NodeUpdateRepositoryProtocol.swift | 5 +- .../Node/RubbishBinRepositoryProtocol.swift | 1 - .../Node/SearchNodeRepositoryProtocol.swift | 1 - ...OfflineFileFetcherRepositoryProtocol.swift | 1 - .../APIEnvironmentRepositoryProtocol.swift | 1 - .../ChangeSfuServerRepositoryProtocol.swift | 1 - .../LogSettingRepositoryProtocol.swift | 1 - .../DownloadFileRepositoryProtocol.swift | 10 + .../TransferInventoryRepositoryProtocol.swift | 1 - .../UploadPhotoAssetsRepositoryProtocol.swift | 1 - .../UserAttributeRepositoryProtocol.swift | 1 - .../User/UserImageRepositoryProtocol.swift | 9 +- .../User/UserInviteRepositoryProtocol.swift | 6 +- .../User/UserStoreRepositoryProtocol.swift | 1 - .../FileVersionsRepositoryProtocol.swift | 1 - .../Video/VideoMediaRepositoryProtocol.swift | 1 - .../UseCase/ABTest/ABTestUseCase.swift | 1 - .../UseCase/Account/AccountUseCase.swift | 1 - .../UseCase/Account/PSAUseCase.swift | 1 - .../UseCase/Account/SMS/CheckSMSUseCase.swift | 1 - .../UseCase/Account/SMS/SMSUseCase.swift | 1 - .../MEGADomain/UseCase/Ads/AdsUseCase.swift | 24 + .../UseCase/Album/AlbumListUseCase.swift | 19 +- .../Analytics/AnalyticsEventUseCase.swift | 1 - .../AudioVideo/AudioSessionUseCase.swift | 1 - .../AudioVideo/CaptureDeviceUseCase.swift | 7 +- .../UseCase/Banner/UserBannerUseCase.swift | 18 +- .../UseCase/Chat/ChatLinkErrorEntity.swift | 1 - .../UseCase/Chat/ChatRoomUseCase.swift | 7 + .../MEGADomain/UseCase/Chat/ChatUseCase.swift | 5 + .../Chat/ClearChatHistoryUseCase.swift | 1 - .../Chat/HistoryRetentionUseCase.swift | 1 - .../Chat/ManageChatHistoryUseCase.swift | 1 - .../UseCase/Chat/WaitingRoomUseCase.swift | 15 + .../UseCase/Contact/ContactsUseCase.swift | 1 - .../Credentials/CredentialUseCase.swift | 1 - .../FeatureFlag/FeatureFlagUseCase.swift | 1 - .../UseCase/MEGASdk/MEGAHandleUseCase.swift | 1 - .../UseCase/Media/VideoMediaUseCase.swift | 1 - .../Network/NetworkMonitorUseCase.swift | 1 - .../UseCase/Node/NodeActionUseCase.swift | 6 +- .../UseCase/Node/NodeAttributeUseCase.swift | 1 - .../Node/SaveMediaToPhotosUseCase.swift | 33 +- .../Preference/PreferenceUseCase.swift | 2 +- .../Settings/APIEnvironmentUseCase.swift | 1 - .../Settings/AppEnvironmentUseCase.swift | 1 - .../UseCase/Settings/ManageLogsUseCase.swift | 1 - .../Transfer/UploadPhotoAssetsUseCase.swift | 1 - .../UseCase/User/UserInviteUseCase.swift | 8 +- .../Versions/FileVersionsUseCase.swift | 1 - .../Entity/AlbumEntity+Test.swift | 13 +- .../Entity/CallEntity+Test.swift | 7 +- .../MockRepos/MockAdsRepository.swift | 23 + .../MockRepos/MockChatRoomRepository.swift | 195 + .../MockDownloadFileRepository.swift | 4 + .../MockRepos/MockNodeActionRepository.swift | 2 +- .../MockCaptureDeviceUseCase.swift | 2 +- .../MockChangeSfuServerUseCase.swift | 1 - .../MockUseCases/MockChatUseCase.swift | 9 +- .../MockClearChatHistoryUseCase.swift | 1 - .../MockHistoryRetentionUseCase.swift | 1 - .../MockUseCases/MockWaitingRoomUseCase.swift | 13 + .../AccountPlanPurchaseUseCaseTests.swift | 16 +- .../MEGADomainTests/AdsUseCaseTests.swift | 24 + .../AlbumListUseCaseTests.swift | 128 +- .../ChatRoomUseCaseTests.swift | 29 + .../ClearChatHistoryUseCaseTests.swift | 1 - .../CookieSettingsUseCaseTests.swift | 1 - .../FileVersionsUseCaseTests.swift | 1 - .../NetworkMonitorUseCaseTests.swift | 1 - .../NodeActionUseCaseTests.swift | 1 - .../NodeAttributeUseCaseTests.swift | 1 - Modules/Domain/MEGAIntentDomain/.gitignore | 9 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + Modules/Domain/MEGAIntentDomain/Package.swift | 30 + Modules/Domain/MEGAIntentDomain/README.md | 3 + .../UseCase/IntentPersonUseCase.swift | 46 + .../IntentPersonUseCaseTests.swift | 90 + .../Package.swift | 8 +- Modules/Features/Search/Package.swift | 27 +- .../Search/Infrastructure/AuxilaryTypes.swift | 17 + .../Search/Infrastructure/SearchBridge.swift | 19 + .../Infrastructure/SearchChipsEntity.swift | 47 + .../Infrastructure/SearchQueryEntity.swift | 31 + .../Search/Infrastructure/SearchResult.swift | 71 + .../Infrastructure/SearchResultsEntity.swift | 15 + .../SearchResultsProviding.swift | 4 + ...NonProductTestSearchrResultsProvider.swift | 30 + .../Search/Sources/Search/Search.swift | 1 - .../Search/UI/SearchResultRowView.swift | 74 + .../Search/UI/SearchResultRowViewModel.swift | 43 + .../Sources/Search/UI/SearchResultsView.swift | 64 + .../Search/UI/SearchResultsViewModel.swift | 84 + .../MockSearchResultsProviding.swift | 52 + .../SearchResultsViewModelTests.swift | 131 + .../Tests/SearchTests/SearchTests.swift | 4 - .../Tests/SearchTests/XCTestHelpers.swift | 11 + .../MEGAFoundation/Package.swift | 6 +- .../AVAudioSession+Additions.swift | 17 + ...itions.swift => CNContact+Additions.swift} | 12 +- .../DispatchQueue+Additions.swift | 0 .../CNContact/CNContactTests.swift | 69 + .../Time/TimeIntervalTests.swift | 1 - .../MEGAPermissions/Package.swift | 8 +- .../MockDevicePermissionsHandler.swift | 13 +- .../Infrastracture/MEGASwift/Package.swift | 6 +- .../Sources/MEGASwift/AsyncValues.swift | 1 - .../Sources/MEGASwift/Result+Additions.swift | 1 - Modules/Infrastracture/MEGATest/Package.swift | 4 +- Modules/Localization/MEGAL10n/.gitignore | 9 + .../xcschemes/MEGALocalization.xcscheme | 66 + Modules/Localization/MEGAL10n/Package.swift | 44 + Modules/Localization/MEGAL10n/README.md | 3 + .../MEGAL10n/Sources/MEGAL10n/Localization.h | 13 + .../MEGAL10n/Sources/MEGAL10n/Localization.m | 9 + .../Sources/MEGAL10n/Localization.swift | 7 + .../Resources}/Base.lproj/Localizable.strings | 42 +- .../Base.lproj/Localizable.stringsdict | 32 + .../Resources}/ar.lproj/Localizable.strings | 48 +- .../ar.lproj/Localizable.stringsdict | 32 + .../Resources}/de.lproj/Localizable.strings | 92 +- .../de.lproj/Localizable.stringsdict | 36 +- .../Resources}/en.lproj/Localizable.strings | 42 +- .../en.lproj/Localizable.stringsdict | 32 + .../Resources}/es.lproj/Localizable.strings | 88 +- .../es.lproj/Localizable.stringsdict | 38 +- .../Resources}/fr.lproj/Localizable.strings | 66 +- .../fr.lproj/Localizable.stringsdict | 36 + .../Resources}/id.lproj/Localizable.strings | 116 +- .../id.lproj/Localizable.stringsdict | 56 +- .../Resources}/it.lproj/Localizable.strings | 42 +- .../it.lproj/Localizable.stringsdict | 32 + .../Resources}/ja.lproj/Localizable.strings | 42 +- .../ja.lproj/Localizable.stringsdict | 28 + .../Resources}/ko.lproj/Localizable.strings | 42 +- .../ko.lproj/Localizable.stringsdict | 32 + .../Resources}/nl.lproj/Localizable.strings | 42 +- .../nl.lproj/Localizable.stringsdict | 32 + .../Resources}/pl.lproj/Localizable.strings | 42 +- .../pl.lproj/Localizable.stringsdict | 36 + .../Resources}/pt.lproj/Localizable.strings | 44 +- .../pt.lproj/Localizable.stringsdict | 36 + .../Resources}/ro.lproj/Localizable.strings | 62 +- .../ro.lproj/Localizable.stringsdict | 126 +- .../Resources}/ru.lproj/Localizable.strings | 58 +- .../ru.lproj/Localizable.stringsdict | 36 + .../Resources}/th.lproj/Localizable.strings | 112 +- .../th.lproj/Localizable.stringsdict | 56 +- .../Resources}/vi.lproj/Localizable.strings | 76 +- .../vi.lproj/Localizable.stringsdict | 30 + .../zh-Hans.lproj/Localizable.strings | 100 +- .../zh-Hans.lproj/Localizable.stringsdict | 42 +- .../zh-Hant.lproj/Localizable.strings | 46 +- .../zh-Hant.lproj/Localizable.stringsdict | 32 + .../Sources/MEGAL10n/Strings+Generated.swift | 6192 +++++++++++++++++ .../MEGAL10n/SwiftGen/swiftgen.yml | 14 + .../Features/DeviceCenter/Package.swift | 2 +- .../BackupList/BackupListView.swift | 44 +- .../BackupList/BackupListViewModel.swift | 95 +- .../BackupList/BackupListViewRouter.swift | 33 +- .../DeviceCenterItemView.swift | 7 +- .../DeviceCenterItemViewModel.swift | 16 +- .../DeviceCenterListContainerView.swift | 53 + .../DeviceList/DeviceListView.swift | 53 +- .../DeviceList/DeviceListViewModel.swift | 107 +- .../DeviceList/DeviceListViewRouter.swift | 20 +- .../Model/Assets/BackupListAssets.swift | 1 - .../Model/Assets/DeviceCenterAction.swift | 23 + .../Model/Assets/DeviceCenterAssets.swift | 5 +- .../Model/Assets/DeviceCenterItemType.swift | 6 + .../Model/Assets/DeviceListAssets.swift | 1 - .../Model/Assets/EmptyStateAssets.swift | 1 - .../Model/Assets/ItemAssets.swift | 1 - .../Model/Assets/SearchAssets.swift | 1 - .../Models/DeviceCenterAction+Test.swift | 10 + .../Routers/BackupListViewRouterTests.swift | 12 +- .../Routers/DeviceListViewRouterTests.swift | 6 +- .../ViewModels/BackupListViewModelTests.swift | 303 +- .../ViewModels/DeviceListViewModelTests.swift | 148 +- .../Features/Settings/Package.swift | 2 +- .../model/APIEnvironmentChangingAlert.swift | 1 - .../Settings/About/model/AppVersion.swift | 1 - .../About/model/ChangeSfuServerAlert.swift | 1 - .../About/model/LogTogglingAlert.swift | 1 - .../TermsAndPoliciesView.swift | 1 - Modules/Presentation/MEGAAssets/Package.swift | 6 +- .../Sources/MEGAAssets/MEGAAssets.swift | 8 + .../cloudDriveFolder.imageset/Contents.json | 25 + .../cloudDriveFolder.imageset/Group 1.pdf | Bin 0 -> 1797 bytes .../cloudDriveFolder.imageset/Group.pdf | Bin 0 -> 1797 bytes .../Images.xcassets/Filetypes/Contents.json | 6 +- .../Contents.json | 0 .../folder_camera_uploads@2x.png | Bin .../folder_camera_uploads@3x.png | Bin .../folder_camera_uploads_dark@2x.png | Bin .../folder_camera_uploads_darkDark@3x.png | Bin .../Contents.json | 0 .../scanQRCode.pdf | Bin .../scanQRCodeDark.pdf | Bin .../Contents.json | 0 .../storage_full.pdf | Bin .../MEGAPresentation/Package.swift | 16 +- .../Extensions/Int+Analytics.swift | 8 + .../FeatureFlag/FeatureFlagKey.swift | 1 + Modules/Repository/ChatRepo/Package.swift | 8 +- .../RequestDelegate/ChatRequestDelegate.swift | 6 +- .../Chat/ManageChatHistoryRepository.swift | 57 + .../Chat/WaitingRoomRepository.swift | 18 + .../MEGAPickerFileProviderRepo/Package.swift | 10 +- Modules/Repository/MEGARepo/Package.swift | 10 +- .../CameraPositionEntity+Mapper.swift | 0 .../Photos}/PhotosLibraryRepository.swift | 17 +- .../AudioVideo/CaptureDeviceRepository.swift | 11 +- .../Contacts/ContactsRepository.swift | 38 + .../Filesystem/FileSystemRepository.swift | 0 .../Preference/PreferenceRepository.swift | 0 .../ContactsRepositoryTests.swift | 3 +- .../xcschemes/MEGASDKRepo.xcscheme | 66 + Modules/Repository/MEGASDKRepo/Package.swift | 12 +- .../Account}/AccountPlanEntity+Mapper.swift | 24 +- .../Account/UserAttributeEntity+Mapper.swift | 38 + .../Mappers/Ads/AdsFlagEntity+Mapper.swift | 16 + .../User/ContactRequestEntity+Mapper.swift | 4 +- .../Analytics/AnalyticsEventProtocol.swift | 1 - .../Account/CookieSettingsRepository.swift | 1 - .../Repository/Ads/AdsRepository.swift | 47 + .../Repository/Banner/BannerRepository.swift | 51 + .../Contact/ContactsRepository.swift | 10 - .../Favourites/FavouriteNodesRepository.swift | 22 +- .../Network/NetworkMonitorRepository.swift | 1 - .../Node/NodeActionRepository.swift | 238 + .../Node/NodeAttributeRepository.swift | 25 +- .../Node/NodeFavouriteActionRepository.swift | 13 +- .../Node/NodeUpdateRepository.swift | 16 +- .../Node/RubbishBinRepository.swift | 27 +- .../User/UserAttributeRepository.swift | 28 +- .../Repository/User/UserImageRepository.swift | 68 +- .../User/UserInviteRepository.swift | 44 + .../Banner/BannerEntity+Mapper.swift | 4 +- .../Banner/BannerListEntity+Mapper.swift | 6 +- .../SDK/RequestDelegate/RequestDelegate.swift | 7 +- .../Provider/MockMEGANodeProvider.swift | 7 +- .../SDK/MockContactRequestList.swift | 6 +- .../MEGASDKRepoMock/SDK/MockNode.swift | 2 + .../MEGASDKRepoMock/SDK/MockRequest.swift | 6 +- .../Sources/MEGASDKRepoMock/SDK/MockSdk.swift | 36 +- .../MEGASDKRepoTests/AdsRepositoryTests.swift | 59 + .../NodeAttributeRepositoryTests.swift | 3 +- .../NodeUpdateRepositoryTests.swift | 2 +- .../RubbishBinRepositoryTests.swift | 5 +- .../ActionSheet/ActionSheetHeaderView.swift | 48 + .../ActionSheet/ActionSheetView.swift | 90 + .../Custom Views/TaskModifier.swift | 87 +- .../Text/FocusableTextFieldView.swift | 66 + .../Text/SubheadlineTextView.swift | 1 - .../Extensions/View+Additions.swift | 2 +- SearchDemo/ContentView.swift | 32 + SearchDemo/SearchDemoApp.swift | 10 + SwiftGen/swiftgen.yml | 11 - fastlane/.env.default | 1 + fastlane/Fastfile | 47 +- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../API/Chat/MEGAArchiveChatRequestDelegate.h | 1 - .../API/Chat/MEGAArchiveChatRequestDelegate.m | 1 - .../Chat/MEGAChatAnswerCallRequestDelegate.h | 1 - .../Chat/MEGAChatAnswerCallRequestDelegate.m | 1 - .../Chat/MEGAChatAttachNodeRequestDelegate.h | 1 - .../Chat/MEGAChatAttachNodeRequestDelegate.m | 1 - .../MEGAChatAttachVoiceClipRequestDelegate.h | 1 - .../MEGAChatAttachVoiceClipRequestDelegate.m | 1 - iMEGA/API/Chat/MEGAChatBaseRequestDelegate.h | 1 - iMEGA/API/Chat/MEGAChatBaseRequestDelegate.m | 7 +- .../MEGAChatChangeGroupNameRequestDelegate.h | 1 - .../MEGAChatChangeGroupNameRequestDelegate.m | 1 - ...EGAChatEnableDisableAudioRequestDelegate.h | 1 - ...EGAChatEnableDisableAudioRequestDelegate.m | 1 - .../API/Chat/MEGAChatGenericRequestDelegate.h | 1 - .../API/Chat/MEGAChatGenericRequestDelegate.m | 1 - iMEGA/API/Chat/MEGAChatNotificationDelegate.h | 1 - iMEGA/API/Chat/MEGAChatNotificationDelegate.m | 1 - .../Chat/MEGAChatStartCallRequestDelegate.h | 1 - .../Chat/MEGAChatStartCallRequestDelegate.m | 1 - .../MEGAContactLinkCreateRequestDelegate.h | 1 - .../MEGAContactLinkCreateRequestDelegate.m | 1 - .../MEGAContactLinkQueryRequestDelegate.h | 1 - .../MEGAContactLinkQueryRequestDelegate.m | 1 - iMEGA/API/Requests/MEGACopyRequestDelegate.h | 1 - iMEGA/API/Requests/MEGACopyRequestDelegate.m | 5 +- .../MEGACreateAccountRequestDelegate.h | 1 - .../MEGACreateAccountRequestDelegate.m | 7 +- .../MEGACreateFolderRequestDelegate.h | 1 - .../MEGACreateFolderRequestDelegate.m | 9 +- .../API/Requests/MEGAExportRequestDelegate.h | 1 - .../API/Requests/MEGAExportRequestDelegate.m | 7 +- .../Requests/MEGAFetchNodesRequestDelegate.h | 1 - .../Requests/MEGAFetchNodesRequestDelegate.m | 1 - .../API/Requests/MEGAGenericRequestDelegate.h | 1 - .../API/Requests/MEGAGenericRequestDelegate.m | 1 - .../Requests/MEGAGetAttrUserRequestDelegate.h | 1 - .../Requests/MEGAGetAttrUserRequestDelegate.m | 1 - .../Requests/MEGAGetPreviewRequestDelegate.h | 1 - .../Requests/MEGAGetPreviewRequestDelegate.m | 1 - .../MEGAGetPublicNodeRequestDelegate.h | 1 - .../MEGAGetPublicNodeRequestDelegate.m | 1 - .../MEGAGetThumbnailRequestDelegate.h | 1 - .../MEGAGetThumbnailRequestDelegate.m | 1 - .../MEGAInviteContactRequestDelegate.h | 1 - .../MEGAInviteContactRequestDelegate.m | 21 +- iMEGA/API/Requests/MEGALoginRequestDelegate.h | 1 - iMEGA/API/Requests/MEGALoginRequestDelegate.m | 15 +- .../MEGALoginToFolderLinkRequestDelegate.h | 1 - .../MEGALoginToFolderLinkRequestDelegate.m | 1 - iMEGA/API/Requests/MEGAMoveRequestDelegate.h | 1 - iMEGA/API/Requests/MEGAMoveRequestDelegate.m | 21 +- .../MEGAMultiFactorAuthCheckRequestDelegate.h | 1 - .../MEGAMultiFactorAuthCheckRequestDelegate.m | 1 - .../MEGAPasswordLinkRequestDelegate.h | 1 - .../MEGAPasswordLinkRequestDelegate.m | 6 +- .../MEGAQueryRecoveryLinkRequestDelegate.h | 1 - .../MEGAQueryRecoveryLinkRequestDelegate.m | 24 +- .../MEGAQuerySignupLinkRequestDelegate.h | 1 - .../MEGAQuerySignupLinkRequestDelegate.m | 16 +- .../MEGARemoveContactRequestDelegate.h | 1 - .../MEGARemoveContactRequestDelegate.m | 9 +- .../API/Requests/MEGARemoveRequestDelegate.h | 1 - .../API/Requests/MEGARemoveRequestDelegate.m | 9 +- .../Requests/MEGASetAttrUserRequestDelegate.h | 1 - .../Requests/MEGASetAttrUserRequestDelegate.m | 1 - iMEGA/API/Requests/MEGAShareRequestDelegate.h | 1 - iMEGA/API/Requests/MEGAShareRequestDelegate.m | 14 +- .../MEGAShowPasswordReminderRequestDelegate.h | 1 - .../MEGAShowPasswordReminderRequestDelegate.m | 1 - .../MEGAPauseTransferRequestDelegate.h | 3 - .../MEGAPauseTransferRequestDelegate.m | 1 - .../MEGAStartDownloadTransferDelegate.h | 1 - .../MEGAStartDownloadTransferDelegate.m | 1 - .../MEGAStartUploadTransferDelegate.h | 1 - .../MEGAStartUploadTransferDelegate.m | 1 - iMEGA/AppDelegate/AppDelegate+Account.swift | 7 +- iMEGA/AppDelegate/AppDelegate+Additions.swift | 50 +- iMEGA/AppDelegate/AppDelegate.m | 79 +- .../AudioPlayer+MetadataLoader.swift | 6 +- .../AudioPlayer/AudioPlayerManager.swift | 45 +- .../AudioPlayerFileToolbarConfigurator.swift | 1 - .../AudioPlayerViewController.swift | 12 +- .../AudioPlayerViewModel.swift | 1 + .../AudioPlayerViewRouter.swift | 65 +- ...dioPlayerViewRouterNodeActionAdapter.swift | 23 + .../AudioPlaylistIndexedDelegate.swift | 1 + .../AudioPlaylistViewController.swift | 1 + .../Domain/AudioPlayerConfigEntity.swift | 2 +- ...ageGuestUserToJoinMegaViewController.swift | 1 + .../EndMeetingOptionsRouter.swift | 2 +- .../EndMeetingOptionsViewViewController.swift | 1 + .../EnterMeetingLinkControllerWrapper.swift | 1 + .../EnterMeetingLinkRouter.swift | 1 + .../MeetingFloatingPanelRouter.swift | 8 +- .../MeetingFloatingPanelViewController.swift | 1 + .../MeetingFloatingPanelViewModel.swift | 14 +- .../MeetingParticipantTableViewCell.swift | 3 +- .../MeetingParticipantViewModel.swift | 1 + .../MeetingParticpiantInfoViewModel.swift | 45 +- .../MeetingParticpiantInfoViewRouter.swift | 8 +- .../QuickAction/CircularView.swift | 1 - .../QuickAction/MeetingQuickActionView.swift | 1 - .../Utils/ParticipantsAddingViewFactory.swift | 1 + .../HangOrEndCallView.swift | 2 +- iMEGA/CallScene/MeetingContainerRouter.swift | 5 +- .../MeetingContainerViewController.swift | 1 - ...tingParticipantsLayoutViewController.swift | 1 + .../MeetingParticipantsLayoutViewModel.swift | 11 +- .../MeetingOptionsMenuViewModel.swift | 1 + .../ParticipantsLayout/Utils/TonePlayer.swift | 1 - .../EmptyMeetingMessageView.swift | 1 - .../Notifications/CallNotificationView.swift | 1 - .../Views/TitleView/CallTitleView.swift | 1 - .../MeetingCreatingViewController.swift | 1 + .../MeetingCreatingViewModel.swift | 7 +- .../MeetingCreatingViewRouter.swift | 6 +- .../Views/WaitingRoomJoinPanelView.swift | 67 +- .../Views/WaitingRoomUserAvatarView.swift | 7 +- .../WaitingRoom/Views/WaitingRoomView.swift | 69 +- .../WaitingRoomViewController.swift | 11 +- .../WaitingRoom/WaitingRoomViewModel.swift | 393 +- .../WaitingRoom/WaitingRoomViewRouter.swift | 67 +- .../DataModels/AssetFetchResult.h | 1 - .../DataModels/AssetFetchResult.m | 1 - .../DataModels/AssetIdentifierInfo.h | 1 - .../DataModels/AssetIdentifierInfo.m | 1 - .../DataModels/AssetLocalAttribute.h | 1 - .../DataModels/AssetLocalAttribute.m | 1 - .../DataModels/AssetUploadInfo.h | 1 - .../DataModels/AssetUploadInfo.m | 1 - .../DataModels/AssetUploadStatus.h | 1 - .../DataModels/AssetUploadStatus.m | 1 - iMEGA/Camera uploads/DataModels/UploadStats.h | 1 - iMEGA/Camera uploads/DataModels/UploadStats.m | 1 - .../PhotosViewController+ContextMenu.swift | 1 + .../PhotosViewController+PhotoLibrary.swift | 5 +- .../FilterView/PhotoLibraryFilterView.swift | 1 + .../MEGACollectionViewFlowLayout.h | 1 - .../MEGACollectionViewFlowLayout.m | 1 - ...mContainerViewController+ContextMenu.swift | 1 + .../PhotoAlbumContainerViewController.swift | 4 +- .../PhotoAlbumContainerViewModel.swift | 5 + .../PhotoAlbum/Tabs/PagerTabViewModel.swift | 1 + .../Camera uploads/PhotosFilterOptions.swift | 1 + iMEGA/Camera uploads/PhotosViewController.h | 1 - iMEGA/Camera uploads/PhotosViewController.m | 47 +- .../TransferResponseValidator.h | 1 - .../TransferResponseValidator.m | 1 - .../TransferSession/TransferSessionDelegate.h | 1 - .../TransferSession/TransferSessionDelegate.m | 1 - .../TransferSession/TransferSessionManager.h | 1 - .../TransferSession/TransferSessionManager.m | 1 - .../TransferSessionTaskDelegate.h | 1 - .../TransferSessionTaskDelegate.m | 1 - .../UploadManagers/AttributeUploadManager.h | 1 - .../UploadManagers/AttributeUploadManager.m | 1 - .../CameraUploadCompletionManager.h | 1 - .../CameraUploadCompletionManager.m | 1 - .../CameraUploadManager+Settings.h | 1 - .../CameraUploadManager+Settings.m | 1 - .../UploadManagers/CameraUploadManager.h | 1 - .../UploadManagers/CameraUploadManager.m | 1 - .../CameraUploadRecordManager.h | 1 - .../CameraUploadRecordManager.m | 1 - .../UploadManagers/ImageExportManager.h | 1 - .../UploadManagers/ImageExportManager.m | 1 - .../AssetResourceUploadOperation.h | 1 - .../AssetResourceUploadOperation.m | 1 - .../AttributeUploadOperation.h | 1 - .../AttributeUploadOperation.m | 1 - .../PreviewUploadOperation.h | 1 - .../PreviewUploadOperation.m | 1 - .../ThumbnailUploadOperation.h | 1 - .../ThumbnailUploadOperation.m | 1 - .../CameraUploadOperation+Utils.h | 1 - .../CameraUploadOperation+Utils.m | 1 - .../UploadOperations/CameraUploadOperation.h | 1 - .../UploadOperations/CameraUploadOperation.m | 1 - .../UploadOperations/ImageExportOperation.h | 1 - .../UploadOperations/ImageExportOperation.m | 1 - .../LivePhotoUploadOperation.h | 1 - .../LivePhotoUploadOperation.m | 1 - .../UploadOperations/LoadMediaInfoOperation.h | 1 - .../UploadOperations/LoadMediaInfoOperation.m | 1 - .../MEGABackgroundTaskOperation.h | 1 - .../MEGABackgroundTaskOperation.m | 1 - .../UploadOperations/MEGAExpirableOperation.h | 1 - .../UploadOperations/MEGAExpirableOperation.m | 1 - .../UploadOperations/MEGAOperation.h | 1 - .../UploadOperations/MEGAOperation.m | 1 - .../NodesFetchListenerOperation.h | 1 - .../NodesFetchListenerOperation.m | 1 - .../UploadOperations/PhotoUploadOperation.h | 1 - .../UploadOperations/PhotoUploadOperation.m | 1 - .../UploadOperations/PutNodeOperation.h | 1 - .../UploadOperations/PutNodeOperation.m | 1 - .../UploadOperations/VideoUploadOperation.h | 1 - .../UploadOperations/VideoUploadOperation.m | 1 - .../BackgroundUploadingTaskMonitor.h | 1 - .../BackgroundUploadingTaskMonitor.m | 1 - .../UploadUtils/CameraScanner.h | 1 - .../UploadUtils/CameraScanner.m | 1 - .../CameraUploadBackgroundRefreshPerformer.h | 1 - .../CameraUploadBackgroundRefreshPerformer.m | 1 - .../CameraUploadConcurrentCountCalculator.h | 1 - .../CameraUploadConcurrentCountCalculator.m | 1 - .../UploadUtils/CameraUploadRequestDelegate.h | 1 - .../UploadUtils/CameraUploadRequestDelegate.m | 1 - .../Categories/AVAsset+CameraUpload.h | 1 - .../Categories/AVAsset+CameraUpload.m | 1 - .../Categories/AVURLAsset+CameraUpload.h | 1 - .../Categories/AVURLAsset+CameraUpload.m | 1 - .../Categories/NSError+CameraUpload.h | 1 - .../Categories/NSError+CameraUpload.m | 1 - .../Categories/NSURL+CameraUpload.h | 1 - .../Categories/NSURL+CameraUpload.m | 1 - .../Categories/PHAssetResource+CameraUpload.h | 1 - .../Categories/PHAssetResource+CameraUpload.m | 1 - .../Categories/PHFetchOptions+CameraUpload.h | 1 - .../Categories/PHFetchOptions+CameraUpload.m | 1 - .../Categories/PHFetchResult+CameraUpload.h | 1 - .../Categories/PHFetchResult+CameraUpload.m | 1 - .../UploadUtils/DiskSpaceDetector.h | 1 - .../UploadUtils/DiskSpaceDetector.m | 1 - .../UploadUtils/FileEncrypter.h | 1 - .../UploadUtils/FileEncrypter.m | 1 - .../Heartbeat/BackupRegister.swift | 1 + .../Heartbeat/HeartbeatRequestDelegate.swift | 2 +- .../UploadUtils/ImageExporter.h | 1 - .../UploadUtils/ImageExporter.m | 1 - .../UploadUtils/LivePhotoScanner.h | 1 - .../UploadUtils/LivePhotoScanner.m | 1 - .../UploadUtils/LocalFileNameGenerator.h | 1 - .../UploadUtils/LocalFileNameGenerator.m | 1 - .../UploadUtils/MediaInfoLoader.h | 1 - .../UploadUtils/MediaInfoLoader.m | 1 - .../UploadUtils/SavedIdentifierParser.h | 1 - .../UploadUtils/SavedIdentifierParser.m | 1 - .../UploadUtils/UploadOperationFactory.h | 1 - .../UploadUtils/UploadOperationFactory.m | 1 - .../UploadUtils/UploadRecordsCollator.h | 1 - .../UploadUtils/UploadRecordsCollator.m | 1 - .../PhotoLibraryFilterViewModel.swift | 1 + .../AddToChatMenuViewController.swift | 1 - .../Chat Messages/AddToChatViewAnimator.swift | 1 - .../Chat Messages/BasicAudioController.swift | 18 +- iMEGA/Chat Messages/ChatMessage.swift | 6 +- .../ChatMessageActionMenuViewController.swift | 9 +- .../ChatNotificationMessage.swift | 2 +- iMEGA/Chat Messages/ChatRoomDelegate.swift | 9 +- .../ChatViewAttachmentCellViewModel.swift | 1 + .../ChatViewController+CallStatus.swift | 21 +- .../ChatViewController+ChatDelegate.swift | 1 - .../ChatViewController+Docscan.swift | 3 +- .../ChatViewController+InputBar.swift | 65 +- .../ChatViewController+Menus.swift | 10 +- ...atViewController+MessageCellDelegate.swift | 17 +- ...ViewController+MessageLayoutDelegate.swift | 8 +- ...hatViewController+MessagesDataSource.swift | 18 +- ...ewController+MessagesDisplayDelegate.swift | 20 +- .../ChatViewController+NavigationBar.swift | 7 +- .../ChatViewController+Toolbar.swift | 7 +- iMEGA/Chat Messages/ChatViewController.swift | 59 +- iMEGA/Chat Messages/MegaDataFormatter.swift | 1 - ...ReactedEmojisUsersListViewController.swift | 7 +- .../ReactedUsersListPageViewController.swift | 1 - .../ViewModel/ChatContentViewModel.swift | 38 +- .../AddToChatAllowAccessCollectionCell.swift | 2 +- .../Views/AddToChatImageCell.swift | 2 +- iMEGA/Chat Messages/Views/AddToChatMenu.swift | 1 - .../Views/AddToChatMenuItemsView.swift | 1 - .../Views/AddToChatMenuView.swift | 4 +- .../Views/Chat Input Bar/ChatInputBar.swift | 1 - .../Text Input/MessageInputBar.swift | 2 +- ...sageInputBarComponentsSizeCalculator.swift | 1 - .../MessageInputTextBackgroundView.swift | 1 - .../Text Input/MessageTextView.swift | 1 + .../Voice Input/AudioRecordingInputBar.swift | 2 +- .../Voice Input/AudioWavesView.swift | 1 - .../Voice Input/EnlargementView.swift | 1 - .../FingerLiftupGestureRecognizer.swift | 1 - .../Voice Input/MeterTable.swift | 1 - .../Voice Input/TapAndHoldMessageView.swift | 2 +- .../Voice Input/VoiceClipInputBar.swift | 1 - .../Views/ChatGiphyCollectionViewCell.swift | 6 +- .../ChatLocationCollectionViewCell.swift | 9 +- .../ChatManagmentTypeCollectionViewCell.swift | 4 +- .../Views/ChatMediaCollectionViewCell.swift | 10 +- ...tRichPreviewDialogCollectionViewCell.swift | 12 +- ...atRichPreviewMediaCollectionViewCell.swift | 32 +- .../Views/ChatTextMessageViewCell.swift | 6 +- iMEGA/Chat Messages/Views/ChatTitleView.swift | 1 + ...hatUnreadMessagesLabelCollectionCell.swift | 3 +- .../Views/ChatViewAttachmentCell.swift | 4 +- .../Views/ChatViewCallCollectionCell.swift | 5 +- .../ChatViewIntroductionHeaderView.swift | 2 +- .../ChatVoiceClipCollectionViewCell.swift | 6 +- .../Views/ContactLinkCollectionViewCell.swift | 6 +- .../Chat Messages/Views/GeoLocationView.swift | 1 - iMEGA/Chat Messages/Views/JoinInputBar.swift | 2 +- .../MessageSizeCalculator+AccessoryView.swift | 1 - .../Views/ReactionView/Emoji.swift | 1 - .../ReactionView/EmojiCarousalView.swift | 1 - .../EmojiReactionCollectionCell.swift | 1 - .../MessageOptionItemTableCell.swift | 1 - .../MessageReactionReusableView.swift | 4 +- .../Views/RichPreviewContentView.swift | 2 +- .../Views/RichPreviewDialogView.swift | 1 + .../Chat/ChatAttachedContactsViewController.h | 1 - .../Chat/ChatAttachedContactsViewController.m | 29 +- iMEGA/Chat/ChatAttachedNodesViewController.h | 1 - iMEGA/Chat/ChatAttachedNodesViewController.m | 33 +- iMEGA/Chat/ChatNotificationControl.swift | 1 + iMEGA/Chat/ChatRoomCell.m | 66 +- iMEGA/Chat/ChatRoomsViewController.m | 57 +- .../Giphy/GiphyPreviewViewController.swift | 1 + iMEGA/Chat/Giphy/GiphySelectionView.swift | 5 +- .../Giphy/GiphySelectionViewController.swift | 5 +- iMEGA/Chat/GroupChatDetailsViewController.m | 118 +- iMEGA/Chat/LocationSearchTableViewCell.h | 1 - iMEGA/Chat/LocationSearchTableViewCell.m | 1 - .../Chat/LocationSearchTableViewController.h | 1 - .../Chat/LocationSearchTableViewController.m | 5 +- iMEGA/Chat/MEGACallManager.h | 1 - iMEGA/Chat/MEGACallManager.m | 1 - iMEGA/Chat/MEGAProviderDelegate.h | 1 - iMEGA/Chat/MEGAProviderDelegate.m | 5 +- iMEGA/Chat/MEGAProviderDelegate.swift | 1 - .../HistoryRetentionPickerViewModel.swift | 1 + ...ManageChatHistoryTableViewController.swift | 1 + .../ManageChatHistoryViewModel.swift | 2 +- .../ManageChatHistoryViewRouter.swift | 4 +- .../Chat/Model/MEGAChatMessage+MNZCategory.h | 1 - .../Chat/Model/MEGAChatMessage+MNZCategory.m | 40 +- iMEGA/Chat/SendLinkToChatsDelegate.swift | 11 +- iMEGA/Chat/SendToViewController.h | 1 - iMEGA/Chat/SendToViewController.m | 29 +- iMEGA/Chat/ShareLocationViewController.h | 1 - iMEGA/Chat/ShareLocationViewController.m | 27 +- ...ttachedNodesViewController+Additions.swift | 11 + .../ChatRoomAvatarViewModel.swift | 1 + .../ChatRoomAvatar/UserAvatarViewModel.swift | 1 + .../ActiveCall/ActiveCallViewModel.swift | 3 +- iMEGA/ChatRoomsListScene/ChatRoomView.swift | 9 +- .../ChatRoomViewModel.swift | 114 +- .../ChatRoomsListRouter.swift | 17 +- .../ChatRoomsListRouting.swift | 3 +- .../ChatRoomsListView.swift | 1 + .../ChatRoomsListViewController.swift | 1 + .../ChatRoomsListViewModel.swift | 13 +- .../ChatRoomContextMenuOption.swift | 1 - .../ChatRoomHybridDescriptionViewState.swift | 1 - .../Data Model/ChatRoomsEmptyViewState.swift | 1 - .../Data Model/ChatRoomsTopRowViewState.swift | 1 - .../FutureMeetingRoomView.swift | 2 +- .../FutureMeetingRoomViewModel.swift | 47 +- .../Views/ChatTabsSelectorView.swift | 1 + iMEGA/ChatRoomsViewController+Additions.swift | 3 +- iMEGA/ChatRoomsViewController+MyAvatar.swift | 1 - iMEGA/ChatRoomsViewController.swift | 5 +- .../ChatSharedItemTableViewCell.swift | 5 +- .../ChatSharedItemsViewController.swift | 25 +- iMEGA/ChatStateListener.swift | 2 +- iMEGA/ChatUploader.swift | 21 +- .../Activities/SendToChatActivity.h | 1 - .../Activities/SendToChatActivity.m | 5 +- .../BrowserViewController+Additions.swift | 10 +- iMEGA/Cloud drive/BrowserViewController.m | 79 +- .../Cells/NodeCollectionViewCell+Nib.swift | 1 - .../Cells/NodeCollectionViewCell.h | 1 - .../Cells/NodeCollectionViewCell.m | 1 - .../Cells/NodePropertyTableViewCell.h | 1 - .../Cells/NodePropertyTableViewCell.m | 1 - .../Cells/NodeTableViewCell+Additions.swift | 5 +- iMEGA/Cloud drive/Cells/NodeTableViewCell.h | 1 - iMEGA/Cloud drive/Cells/NodeTableViewCell.m | 3 +- .../Cells/NodeTappablePropertyTableViewCell.h | 1 - .../Cells/NodeTappablePropertyTableViewCell.m | 1 - .../Cells/ThumbnailViewerTableViewCell.h | 1 - .../Cells/ThumbnailViewerTableViewCell.m | 11 +- .../CloudDriveCollectionViewController.h | 1 - .../CloudDriveCollectionViewController.m | 4 +- ...udDriveTableViewController+Additions.swift | 9 +- .../CloudDriveTableViewController.h | 1 - .../CloudDriveTableViewController.m | 8 +- .../CloudDriveViewController+Additions.swift | 3 +- .../CloudDriveViewController+Backup.swift | 9 +- ...loudDriveViewController+CameraUpload.swift | 13 +- ...CloudDriveViewController+ContextMenu.swift | 7 +- .../CloudDriveViewController+EmptyState.swift | 3 +- ...udDriveViewController+MedisDiscovery.swift | 1 + .../CloudDriveViewController+MyAvatar.swift | 1 - ...ler+NodeActionViewControllerDelegate.swift | 9 +- iMEGA/Cloud drive/CloudDriveViewController.m | 40 +- iMEGA/Cloud drive/CloudDriveViewModel.swift | 1 + .../EnforceCopyrightWarningView.swift | 1 + .../EnforceCopyrightWarningViewModel.swift | 1 + .../DocScannerActionTableViewCell.swift | 2 +- .../DocScannerDetailTableCell.swift | 2 +- .../DocScannerFileNameTableCell.swift | 1 - ...cannerSaveSettingTableViewController.swift | 15 +- .../GetLinkAccessInfoTableViewCell.swift | 1 + .../Cells/GetLinkAlbumInfoCellViewModel.swift | 1 + .../Cells/GetLinkDetailTableViewCell.swift | 1 + .../GetLinkSwitchOptionTableViewCell.swift | 1 + .../DecryptionKeysViewController.swift | 1 + .../GetLink/GetAlbumLinkViewModel.swift | 11 +- .../GetLink/GetAlbumsLinksViewModel.swift | 11 +- .../GetLink/GetAlbumsLinksViewWrapper.swift | 7 +- .../GetLink/GetLinkViewController.swift | 27 +- .../SetLinkPasswordViewController.swift | 5 +- .../ShareAlbumLinkInitialSections.swift | 1 + .../MediaDiscoveryViewController.swift | 1 + .../Cells/NodeInfoActionTableViewCell.swift | 1 + .../Cells/NodeInfoDetailTableViewCell.swift | 7 +- .../Cells/NodeInfoPreviewTableViewCell.swift | 7 +- .../NodeInfoVerifyAccountTableViewCell.swift | 1 + .../Cells/NodeOwnerInfoTableViewCell.swift | 1 + .../NodeInfo/NodeInfoViewController.swift | 2 +- ...NodeVersionsViewController+Additions.swift | 9 + .../NodeInfo/NodeVersionsViewController.h | 1 - .../NodeInfo/NodeVersionsViewController.m | 44 +- .../SearchOperation+Initialize.swift | 2 +- .../Cloud drive/Operations/SearchOperation.h | 1 - .../Cloud drive/Operations/SearchOperation.m | 1 - .../Extensions/MEGAIntent/IntentHandler.swift | 16 +- .../StartCall/IntentHandler+StartCall.swift | 46 +- .../StartCall/IntentPersonProvider.swift | 45 - .../MEGANotifications-Bridging-Header.h | 1 - .../MEGANotifications-PrefixHeader.pch | 1 - .../NotificationService+ScheduleMeeting.swift | 3 +- .../NotificationService.swift | 68 +- .../ScheduleMeetingNotificationInfo.swift | 1 - .../UNNotificationContent+Additions.swift | 1 - .../DocumentActionViewController.swift | 1 + .../FileProviderEnumerator.swift | 2 +- .../FileProviderItem.swift | 1 + .../MEGAPickerFileProvider-Bridging-Header.h | 1 - ...xtensionCancellableTransferViewModel.swift | 1 + .../ExtensionAppearanceManager.swift | 1 - .../MEGAShare/Model/ShareAttachment.h | 1 - .../MEGAShare/Model/ShareAttachment.m | 1 - .../MEGAShare/OpenAppRequiredViewController.h | 1 - .../MEGAShare/OpenAppRequiredViewController.m | 9 +- ...leViewController+TableViewDataSource.swift | 1 + .../ShareDestinationTableViewController.h | 1 - .../ShareDestinationTableViewController.m | 5 +- .../ShareViewController+Additions.swift | 1 + ...reViewController+OpenAppRequiredView.swift | 2 + .../MEGAShare/ShareViewController.h | 1 - .../MEGAShare/ShareViewController.m | 20 +- .../MEGAShare-Bridging-Header.h | 1 - .../MEGAShare-PrefixHeader.pch | 1 - .../Colors/#00A886.colorset/Contents.json | 20 - iMEGA/Extensions/MEGAWidget/MEGAWidget.swift | 1 - .../FavouritesQuickAccessWidget.swift | 1 + .../Model/QuickAccessItemModel.swift | 1 - .../Model/QuickAccessWidgetEntry.swift | 1 - .../QuickAccess/Model/SectionDetail.swift | 1 + .../QuickAccess/Model/WidgetStatus.swift | 1 - .../Offline/OfflineQuickAccessWidget.swift | 1 + .../Recents/RecentsQuickAccessWidget.swift | 1 + .../QuickAccess/Views/DetailItemView.swift | 1 - .../QuickAccess/Views/GridView.swift | 1 - .../Views/QuickAccessWidgetView.swift | 4 +- .../MEGAWidget/Shortcuts/ShortcutDetail.swift | 3 +- .../Shortcuts/ShortcutsWidget.swift | 2 +- .../MEGAWidget-Bridging-Header.h | 1 - iMEGA/GroupChatDetailsViewController.swift | 19 +- iMEGA/Home/Recents/RecentsViewController.h | 1 - iMEGA/Home/Recents/RecentsViewController.m | 20 +- .../SDK/Banner/BannerRepository.swift | 56 - .../SDKNodeClipboardOperationRepository.swift | 1 - .../SDK/SDKTransferListenerRepository.swift | 1 - .../FavouritesScene/FavouritesRouter.swift | 3 +- .../Scenes/Home/View/HomeViewController.swift | 11 +- .../Home/View/Pannel/SlidePanelView.swift | 1 + .../Home/ViewModel/BannerViewModel.swift | 1 + .../Home/ViewModel/MyAvatarViewModel.swift | 2 +- .../ViewModel/RecentActionViewModel.swift | 2 - .../View/ExplorerBaseViewController.swift | 2 +- .../FilesExplorerContainerGridViewState.swift | 1 - .../FilesExplorerContainerListViewState.swift | 1 - .../FilesExplorerContainerViewState.swift | 5 +- .../View/ExplorerToolbarConfigurator.swift | 3 +- ...FavouriteExplorerToolbarConfigurator.swift | 1 - ...FilesExplorerContainerViewController.swift | 1 + .../FilesExplorerListViewController.swift | 1 - .../SearchController/MEGASearchBarView.swift | 21 +- .../AudioExploreViewConfiguration.swift | 1 + .../DocumentExplorerViewConfiguration.swift | 1 + .../FavouritesExplorerGridSource.swift | 5 +- .../FavouritesExplorerViewConfiguration.swift | 1 + .../HomeExplore/ViewModel/FilterType.swift | 2 + .../HomeExplore/ViewModel/SortOrderType.swift | 3 +- .../VideoExplorerViewConfiguration.swift | 1 + iMEGA/Home/Scenes/HomeLocalisation.swift | 1 + iMEGA/Home/Scenes/HomeScreenFactory.swift | 164 +- .../Provider/HomeSearchResultsProviding.swift | 59 + .../View/HomeSearchResultViewController.swift | 16 +- .../ViewModel/HomeSearchResultViewModel.swift | 4 +- iMEGA/Home/Scenes/SearchResultsBridge.swift | 32 + iMEGA/Home/UseCase/Domain/BannerEntity.swift | 14 - .../UseCase/Node/FilesDownloadUseCase.swift | 1 - .../Node/NodeClipboardOperationUseCase.swift | 1 - .../NodeDetails/MockNodeDetailsUseCase.swift | 23 + .../NodeDetailsUseCase.swift | 0 .../Node/NodeThumbnailHomeUseCase.swift | 1 + .../MockSearchFileUseCase.swift | 24 + .../SearchFileUseCase.swift | 0 .../AlbumContent/AlbumContentRouter.swift | 1 + ...bumContentViewController+ContextMenu.swift | 1 + .../AlbumContentViewController.swift | 1 + .../AlbumContent/AlbumContentViewModel.swift | 13 +- .../AlbumCover/AlbumCoverPickerView.swift | 1 + .../AlbumToolbarConfigurator.swift | 1 - .../AlbumContent/AlbumToolbarProvider.swift | 11 +- .../AlbumContentPickerView.swift | 1 + .../AlbumContentPickerViewModel.swift | 7 +- .../Album Library/AlbumListView.swift | 2 +- .../Album Library/AlbumListViewModel.swift | 38 +- .../Album Library/AlbumListViewRouter.swift | 2 + .../Album Library/AlbumNameValidator.swift | 1 + .../Album Library/AlbumSelection.swift | 1 - .../Alert/UIAlertController+Extension.swift | 1 + .../Album Library/Cell/AlbumCell.swift | 7 +- .../Cell/AlbumCellViewModel.swift | 22 +- .../Album Library/Cell/CreateAlbumCell.swift | 1 + .../Cell/CreateAlbumCellViewModel.swift | 2 +- .../Album Library/EmptyAlbumView.swift | 1 + .../Extensions/AlbumEntity+Analytics.swift | 10 + .../Links/ImportAlbumView.swift | 19 +- .../Links/ImportAlbumViewModel.swift | 127 +- .../Card View/PhotoLibraryModeCardView.swift | 3 +- .../Data Model/PhotoLibraryViewMode.swift | 1 + .../Photo Library/Day View/PhotoDayCard.swift | 6 - .../Day View/PhotoLibraryDayView.swift | 6 - .../Month View/PhotoLibraryMonthView.swift | 6 - .../Month View/PhotoMonthCard.swift | 6 - .../PhotoLibraryContentConfiguration.swift | 1 - .../PhotoLibraryContentView.swift | 3 - .../PhotoLibraryContentViewModel.swift | 1 + .../Photo Library/PhotoLibraryProvider.swift | 5 +- .../Year View/PhotoLibraryYearView.swift | 6 - .../Options/SlideShowOptionDetailView.swift | 1 + .../Options/SlideShowOptionRouter.swift | 1 + .../Options/SlideShowOptionViewModel.swift | 1 + .../Views/SlideShowViewController.swift | 1 + .../ChatRoomLinkNonHostView.swift | 2 +- .../ChatRoomLink/ChatRoomLinkView.swift | 1 + .../ChatRoomLink/ChatRoomLinkViewModel.swift | 2 +- .../ChatRoomNotificationsView.swift | 1 + .../ChatRoomParticipantView.swift | 1 + .../ChatRoomParticipantViewModel.swift | 1 + .../ChatRoomParticipantsListView.swift | 1 + .../ChatRoomParticipantPrivilege.swift | 2 + .../Views/AddParticipantsView.swift | 1 + .../Views/SeeMoreParticipantsView.swift | 1 + .../MeetingInfoScene/MeetingInfoRouter.swift | 7 +- iMEGA/MeetingInfoScene/MeetingInfoView.swift | 1 + .../MeetingInfoViewController.swift | 1 + .../ScheduledMeetingDateBuilder.swift | 157 +- .../Views/MeetingDescriptionView.swift | 1 + .../MeetingInfoWaitingRoomSettingView.swift | 1 + .../Views/WaitingRoomWarningBannerView.swift | 26 +- .../Account Hall/MyAccountHallPlanView.swift | 1 + .../Account Hall/MyAccountHallTableViewCell.h | 1 - .../Account Hall/MyAccountHallTableViewCell.m | 9 +- ...MyAccountHallViewController+Addition.swift | 1 + ...llViewController+TableViewDataSource.swift | 9 +- ...HallViewController+TableViewDelegate.swift | 41 +- .../MyAccountHallViewController.m | 14 +- .../ViewModel/AccountHallViewModel.swift | 44 +- ...mentsDetailsViewController+Additions.swift | 1 + .../AchievementsDetailsViewController.h | 1 - .../AchievementsDetailsViewController.m | 9 +- .../Achievements/AchievementsTableViewCell.h | 1 - .../Achievements/AchievementsTableViewCell.m | 1 - ...AchievementsViewController+Additions.swift | 2 + .../Achievements/AchievementsViewController.h | 1 - .../Achievements/AchievementsViewController.m | 26 +- .../InviteFriendsViewController.m | 15 +- ...eYourFriendsViewController+Additions.swift | 2 + ...BonusesTableViewController+Additions.swift | 2 + .../ReferralBonusesTableViewController.h | 1 - .../ReferralBonusesTableViewController.m | 7 +- .../Cells/ContactRequestsTableViewCell.h | 1 - .../Cells/ContactRequestsTableViewCell.m | 1 - .../Contacts/Cells/ContactTableViewCell.m | 14 +- .../Contacts/Cells/ItemCollectionViewCell.h | 1 - .../Contacts/Cells/ItemCollectionViewCell.m | 1 - .../Contacts/ContactDetailsViewController.h | 1 - .../Contacts/ContactDetailsViewController.m | 66 +- .../ContactDetailsViewController.swift | 5 +- .../ContactLinkQRViewController.h | 1 - .../ContactLinkQRViewController.m | 31 +- .../Contacts/ContactRequestsViewController.m | 35 +- .../ContactsGroupTableViewCell.swift | 3 +- .../ContactsGroupsViewController.swift | 26 +- .../ContactsPicker/ContactsPicker.storyboard | 171 - .../ContactsPickerViewController.swift | 345 - .../DeviceContactTableViewCell.swift | 35 - .../DeviceContactsManager.swift | 101 - .../ContactsViewController+Additions.swift | 9 + .../Contacts/ContactsViewController.m | 129 +- .../EnterEmailViewController.swift | 47 +- .../InviteContactViewController.swift | 41 +- .../ItemList/ItemListViewController.h | 1 - .../Contacts/MeetingAlreadyExistsAlert.swift | 1 + .../Contacts/NicknameViewController.swift | 29 +- ...yCredentialsViewController+Additions.swift | 1 + .../VerifyCredentialsViewController.m | 16 +- .../Views/ContactsTableViewHeader.swift | 8 +- .../DiskFullBlockingViewModel.swift | 1 + .../Launch/InitialLaunchViewController.h | 1 - .../Launch/InitialLaunchViewController.m | 17 +- .../My Account/Launch/LaunchViewController.h | 1 - .../My Account/Launch/LaunchViewController.m | 9 +- ...CheckEmailAndFollowTheLinkViewController.h | 1 - ...CheckEmailAndFollowTheLinkViewController.m | 35 +- .../Login/ConfirmAccountViewController.m | 40 +- ...mAccountViewcontroller+DeleteAccount.swift | 4 +- ...reateAccountViewController+Additions.swift | 2 + .../Login/CreateAccountViewController.m | 51 +- iMEGA/My Account/Login/LoginViewController.h | 1 - iMEGA/My Account/Login/LoginViewController.m | 22 +- .../MainTabBarController+Additions.swift | 12 + .../Login/MainTabBarController+CameraUpload.h | 1 - .../Login/MainTabBarController+CameraUpload.m | 11 +- .../Login/MainTabBarController+SnackBar.swift | 1 - iMEGA/My Account/Login/MainTabBarController.m | 12 +- .../Notifications/NotificationTableViewCell.h | 1 - .../Notifications/NotificationTableViewCell.m | 5 +- ...cationsTableViewController+Additions.swift | 5 +- ...icationsTableViewController+Meetings.swift | 1 + .../NotificationsTableViewController.h | 1 - .../NotificationsTableViewController.m | 90 +- ...cheduleMeetingOccurrenceNotification.swift | 1 + ...CollectionViewController+ContextMenu.swift | 1 + ...CollectionViewController+DynamicType.swift | 3 +- .../Offline/OfflineCollectionViewController.h | 1 - .../Offline/OfflineCollectionViewController.m | 1 - .../My Account/Offline/OfflineTableViewCell.h | 1 - ...OfflineTableViewController+Additions.swift | 1 + .../Offline/OfflineTableViewViewController.h | 1 - .../Offline/OfflineTableViewViewController.m | 1 - .../OfflineViewController+Additions.swift | 9 + .../OfflineViewController+ContextMenu.swift | 1 + .../Offline/OfflineViewController.m | 37 +- iMEGA/My Account/PSA/PSAView.swift | 1 + .../ChangePasswordViewController.m | 78 +- .../ChangeNameViewController+Additions.swift | 4 +- .../ChangeName/ChangeNameViewController.h | 1 - .../ChangeName/ChangeNameViewController.m | 16 +- .../Profile/LogoutTableViewCell.swift | 1 - .../Profile/PhoneNumberViewController.swift | 17 +- .../Profile/ProfileTableViewCell.swift | 1 - .../Profile/ProfileTableViewDataSource.swift | 11 +- .../ProfileTableViewDiffableDataSource.swift | 8 +- .../Profile/ProfileViewController.swift | 11 +- .../Profile/RecoveryKeyTableViewCell.swift | 1 - .../AddPhoneNumberViewController.swift | 1 + .../AddPhoneNumberViewModel.swift | 1 + .../VerificationCodeViewController.swift | 1 + .../VerificationCodeViewModel.swift | 1 + .../RegionListViewController.swift | 1 + .../SMSVerificationViewController.swift | 1 + .../SMSVerificationViewModel.swift | 1 + .../AboutTableViewController+Additions.swift | 2 +- .../Settings/About/AboutTableViewController.m | 15 +- ...dvancedTableViewController+Additions.swift | 1 + .../Advanced/AdvancedTableViewController.m | 13 +- .../AppearanceTableViewController.swift | 1 + .../DefaultTabTableViewController.swift | 4 +- ...ortingAndViewModeTableViewController.swift | 2 +- .../Calls/CallsSettingsViewRouter.swift | 3 +- .../CallsSettingsSoundNotificationsView.swift | 1 + ...ameraUploadAdvancedOptionsViewController.h | 1 - ...ameraUploadAdvancedOptionsViewController.m | 31 +- ...UploadsTableViewController+Additions.swift | 1 + .../CameraUploadsTableViewController.m | 42 +- .../VideoUploadsQualityTableViewController.h | 1 - .../VideoUploadsQualityTableViewController.m | 13 +- ...UploadsTableViewController+Additions.swift | 2 + .../VideoUploadsTableViewController.h | 1 - .../VideoUploadsTableViewController.m | 29 +- .../ChatImageQualityTableViewController.swift | 1 + .../Chat/ChatSettingsTableViewController.m | 40 +- .../ChatSettingsTableViewController.swift | 1 - ...tStatusTableViewController+Additions.swift | 1 + .../Chat/ChatStatusTableViewController.m | 34 +- .../ChatVideoQualityTableViewController.h | 1 - .../ChatVideoQualityTableViewController.m | 13 +- .../Settings/Chat/DND/DNDTurnOnOption.swift | 1 + .../Chat/DND/PushNotificationControl.swift | 9 +- .../CookieSettingsTableViewController.swift | 2 +- .../CookieSettingsViewModel.swift | 1 + .../Delete Account/DeleteAccountRouter.swift | 5 +- .../FileVersioningTableViewController.swift | 1 + .../FileVersioningViewRouter.swift | 3 +- .../FileManagementTableViewController.m | 37 +- .../RubbishBinTableViewController.m | 38 +- .../Settings/Help/HelpTableViewController.h | 1 - .../Settings/Help/HelpTableViewController.m | 15 +- .../Help/Report Issue/ReportIssueView.swift | 2 +- .../Report Issue/ReportIssueViewModel.swift | 1 + .../Report Issue/ReportIssueViewRouter.swift | 3 +- .../Help/Report Issue/UploadLogFileView.swift | 2 +- .../Send Feedback/SendFeedbackViewModel.swift | 1 + .../SendFeedbackViewRouter.swift | 1 + .../QASettings/QASettingsRouter.swift | 5 +- .../QASettings/QASetttingsViewModel.swift | 3 +- .../Security/MasterKeyViewController.m | 11 +- .../Passcode/PasscodeTableViewController.m | 17 +- ...scodeTimeDurationTableViewController.swift | 1 + .../Security/QRSettingsTableViewController.h | 1 - .../Security/QRSettingsTableViewController.m | 17 +- .../SecurityOptionsTableViewController.m | 27 +- .../Security/SecuritySettingsViewRouter.swift | 1 + ...ledTwoFactorAuthenticationViewController.h | 1 - ...ledTwoFactorAuthenticationViewController.m | 21 +- ...ingTwoFactorAuthenticationViewController.h | 1 - ...ingTwoFactorAuthenticationViewController.m | 23 +- ...oFactorAuthenticationTableViewController.h | 1 - ...oFactorAuthenticationTableViewController.m | 9 +- .../Settings/SettingViewRouter.swift | 1 + ...ettingsTableViewController+Additions.swift | 2 + .../Settings/SettingsTableViewController.m | 4 +- .../Settings/SettingsViewModel.swift | 1 + .../TermsAndPoliciesRouter.swift | 2 +- .../Spotlight/SpotlightIndexer.swift | 3 +- .../TwoFactorAuthenticationViewController.h | 1 - .../TwoFactorAuthenticationViewController.m | 19 +- .../AccountPlanPurchaseRepository.swift | 1 - .../MEGAPurchase+PromotedPlan.swift | 183 + .../Repository/MEGAPurchase/MEGAPurchase.h | 5 +- .../Repository/MEGAPurchase/MEGAPurchase.m | 56 +- .../Sections/PlanHeaderTagView.swift | 1 + .../Plan View/Sections/PlanPricingView.swift | 3 +- .../Plan View/Sections/PlanStorageView.swift | 1 + .../Data/PlanSelectionSnackBarType.swift | 1 + .../Data/UpgradeAccountPlanAlertType.swift | 3 +- .../UpgradeSectionFeatureOfProView.swift | 1 + .../Sections/UpgradeSectionHeaderView.swift | 9 +- .../UpgradeSectionSubscriptionView.swift | 1 + .../UpgradeAccountPlanView.swift | 3 +- .../UpgradeAccountPlanViewModel.swift | 16 +- .../Upgrade/ProductDetailTableViewCell.h | 1 - .../Upgrade/ProductDetailTableViewCell.m | 2 - .../Upgrade/ProductDetailViewController.h | 1 - .../Upgrade/ProductDetailViewController.m | 44 +- .../Upgrade/UpgradeAccountFactory.swift | 1 - .../Upgrade/UpgradeAccountRouter.swift | 2 +- ...UpgradeTableViewController+Additions.swift | 3 +- .../Upgrade/UpgradeTableViewController.m | 65 +- .../Usage/UsageViewController+Additions.swift | 15 +- iMEGA/My Account/Usage/UsageViewController.m | 10 +- .../FileLinkViewController+Additions.swift | 1 - .../Node/Links/File/FileLinkViewController.m | 27 +- ...CollectionViewController+DynamicType.swift | 3 +- .../FolderLinkCollectionViewController.swift | 1 + .../FolderLinkTableViewController.swift | 7 +- .../FolderLinkViewController+Additions.swift | 9 + .../FolderLinkViewController+SnackBar.swift | 1 - .../Links/Folder/FolderLinkViewController.m | 47 +- iMEGA/Node/Links/UnavailableLinkView.m | 30 +- .../ActionsView/ActionViewModel.swift | 1 + .../HeaderView/HeaderViewModel.swift | 1 + .../NameCollisionRouter.swift | 13 +- .../NameCollisionView/NameCollisionView.swift | 1 + .../NameCollisionViewModel.swift | 37 +- .../Photo browser/MEGAPhotoBrowserAnimator.h | 1 - .../Photo browser/MEGAPhotoBrowserAnimator.m | 1 - ...MEGAPhotoBrowserPickerCollectionViewCell.h | 1 - ...MEGAPhotoBrowserPickerCollectionViewCell.m | 1 - .../MEGAPhotoBrowserPickerViewController.h | 1 - .../MEGAPhotoBrowserPickerViewController.m | 6 +- ...PhotoBrowserViewController+Additions.swift | 28 +- ...APhotoBrowserViewController+LiveText.swift | 3 +- ...serViewController+SnackBarPresenting.swift | 34 + .../MEGAPhotoBrowserViewController.h | 3 +- .../MEGAPhotoBrowserViewController.m | 24 +- .../PhotoBrowserDataProvider.swift | 10 + .../PhotosAppBrowser/DataModel/Album.swift | 1 - .../Miscellaneous/AssetDownloader.swift | 1 - ...ontroller+PhotoAlbumBrowserAdditions.swift | 1 - .../HelperViews/PhotoCarouselVideoIcon.swift | 1 - .../PhotoCollectionBottomView.swift | 1 - .../HelperViews/PhotoSelectedMarkerView.swift | 1 - .../SingleTapHandlerProtocol.swift | 1 - .../Miscellaneous/SingleTapView.swift | 1 - .../AlbumsSelectionActionType.swift | 1 + .../AlbumsTableViewDataSource.swift | 1 - .../PhotoAlbums/AlbumsTableViewDelegate.swift | 1 - .../PhotoAlbums/Cell/AlbumTableViewCell.swift | 1 - .../AlbumsTableViewController.swift | 1 + .../Cell/PhotoCarouselCell.swift | 1 - .../PhotoCarouselViewController.swift | 4 +- .../FlowLayout/PhotoCarouselFlowLayout.swift | 1 - .../PhotoCarouselDataSource.swift | 1 - .../PhotoCarousel/PhotoCarouselDelegate.swift | 1 - .../Cell/PhotoGridViewCell.swift | 1 - .../FlowLayout/PhotoGridViewFlowLayout.swift | 1 - .../PhotoGridViewDataSource.swift | 1 - .../PhotoGridView/PhotoGridViewDelegate.swift | 1 - .../SharedItemsViewController+Additions.swift | 43 +- ...haredItemsViewController+ContextMenu.swift | 1 + ...haredItemsViewController+EmptyStates.swift | 1 + ...aredItemsViewController+TabSelection.swift | 1 - .../Shared Items/SharedItemsViewController.m | 58 +- ...CancellableTransferControllerWrapper.swift | 1 + .../CancellableTransferViewModel.swift | 1 + .../ViewEntity/CancellableTransferType.swift | 1 - .../ViewEntity/TransferStage.swift | 1 - iMEGA/Node/Transfers/TransferTableViewCell.m | 30 +- ...nsfersWidgetViewController+Additions.swift | 36 +- .../Transfers/TransfersWidgetViewController.m | 49 +- .../RemovalConfirmationMessageGenerator.swift | 2 + ...duleMeetingCreationCustomOptionsView.swift | 1 + ...heduleMeetingCreationFrequencyOption.swift | 1 + ...uleMeetingCreationIntervalFooterNote.swift | 1 + ...reationMonthlyCustomOptionsViewModel.swift | 1 + ...eduleMeetingCreationRecurrenceOption.swift | 1 + ...MeetingCreationRecurrenceOptionsView.swift | 1 + ...eduleMeetingEndRecurrenceOptionsView.swift | 1 + .../ScheduleMeetingNewViewConfiguration.swift | 1 + .../ScheduleMeetingPushNotifications.swift | 1 + ...eduleMeetingSelectedFrequencyDetails.swift | 1 + ...ingUpdateOccurrenceViewConfiguration.swift | 1 + ...heduleMeetingUpdateViewConfiguration.swift | 1 + .../ScheduleMeetingView.swift | 2 +- .../ScheduleMeetingViewController.swift | 1 + .../ScheduleMeetingViewModel.swift | 1 + .../Views/DatePickerView.swift | 1 - .../Views/DetailDisclosureView.swift | 1 - ...ngCreationCustomOptionsFrequencyView.swift | 1 + ...tionCustomOptionsRepetitionRulesView.swift | 1 + ...MeetingCreationDateAndRecurrenceView.swift | 2 +- ...heduleMeetingCreationDescriptionView.swift | 2 +- ...cheduleMeetingCreationInvitationView.swift | 2 +- ...tingCreationMonthlyCustomOptionsView.swift | 1 + .../ScheduleMeetingCreationNameView.swift | 18 +- ...cheduleMeetingCreationOpenInviteView.swift | 2 +- ...cheduleMeetingCreationPropertiesView.swift | 2 +- ...heduleMeetingCreationWaitingRoomView.swift | 1 + .../Views/TextDescriptionView.swift | 2 +- .../Views/TextFieldView.swift | 39 - .../WeekDaysInformation.swift | 1 - .../WeekNumberInformation.swift | 2 + .../Model/OccurrenceContextMenuOption.swift | 1 - .../Model/ScheduleMeetingOccurence.swift | 1 - ...ScheduledMeetingOccurrencesViewModel.swift | 1 + .../Views/SeeMoreOccurrencesView.swift | 1 + iMEGA/Supporting Files/MEGA-PrefixHeader.pch | 1 - iMEGA/Utils/AppearanceManager.swift | 1 - .../BackupNodesValidator.swift | 1 + iMEGA/Utils/CallActionManager.swift | 27 +- .../Categories/AVAudioSession+MNZCategory.h | 14 - .../Categories/AVAudioSession+MNZCategory.m | 29 - .../Utils/Categories/CGPoint+Additions.swift | 1 - iMEGA/Utils/Categories/Date+Additions.swift | 1 - .../Categories/MEGAChatCall+Additions.swift | 1 - .../MEGAChatMessage+Additions.swift | 1 + .../MEGAChatPeerList+Additions.swift | 1 - .../Categories/MEGAChatRoom+Additions.swift | 19 +- .../Utils/Categories/MEGAError+MNZCategory.h | 1 - .../Utils/Categories/MEGAError+MNZCategory.m | 1 - iMEGA/Utils/Categories/MEGANode+MNZCategory.h | 1 - iMEGA/Utils/Categories/MEGANode+MNZCategory.m | 59 +- .../Categories/MEGANode+MenuActions.swift | 1 - .../Categories/MEGANodeList+MNZCategory.h | 1 - .../Categories/MEGANodeList+MNZCategory.m | 1 - iMEGA/Utils/Categories/MEGASdk+MNZCategory.h | 1 - iMEGA/Utils/Categories/MEGASdk+MNZCategory.m | 1 - .../Categories/MEGATransfer+MNZCategory.h | 1 - .../Categories/MEGATransfer+MNZCategory.m | 1 - .../Utils/Categories/MEGAUser+Additions.swift | 5 +- iMEGA/Utils/Categories/MEGAUser+MNZCategory.h | 1 - iMEGA/Utils/Categories/MEGAUser+MNZCategory.m | 1 - iMEGA/Utils/Categories/MegaNode+Display.swift | 1 + iMEGA/Utils/Categories/NSArray+MNZCategory.m | 1 - .../NSAttributedString+MNZCategory.h | 1 - .../NSAttributedString+MNZCategory.m | 1 - iMEGA/Utils/Categories/NSDate+MNZCategory.h | 1 - iMEGA/Utils/Categories/NSDate+MNZCategory.m | 1 - .../Categories/NSFileManager+MNZCategory.h | 1 - .../Categories/NSFileManager+MNZCategory.m | 1 - .../Utils/Categories/NSString+Additions.swift | 1 - iMEGA/Utils/Categories/NSString+MNZCategory.h | 1 - iMEGA/Utils/Categories/NSString+MNZCategory.m | 87 +- iMEGA/Utils/Categories/NSURL+MNZCategory.h | 1 - iMEGA/Utils/Categories/NSURL+MNZCategory.m | 5 +- iMEGA/Utils/Categories/SIMD2+Additions.swift | 1 - .../Categories/TimeInterval+Additions.swift | 2 +- .../UIActivityIndicatorView+Additions.swift | 1 - .../UIAlertController+Additions.swift | 2 + .../UIAlertController+InAppPurchase.swift | 4 +- .../Categories/UIApplication+Additions.swift | 1 - .../Categories/UIApplication+MNZCategory.h | 1 - .../Categories/UIApplication+MNZCategory.m | 1 - .../Utils/Categories/UIButton+Additions.swift | 1 - .../Categories/UICollectionView+MNZCategory.h | 1 - .../Categories/UICollectionView+MNZCategory.m | 1 - .../Utils/Categories/UIColor+Additions.swift | 1 - iMEGA/Utils/Categories/UIColor+MNZCategory.h | 1 - iMEGA/Utils/Categories/UIColor+MNZCategory.m | 1 - iMEGA/Utils/Categories/UIDevice+MNZCategory.h | 1 - iMEGA/Utils/Categories/UIDevice+MNZCategory.m | 1 - iMEGA/Utils/Categories/UIFont+MNZCategory.h | 1 - iMEGA/Utils/Categories/UIFont+MNZCategory.m | 1 - iMEGA/Utils/Categories/UIImage+MNZCategory.h | 1 - iMEGA/Utils/Categories/UIImage+MNZCategory.m | 3 +- .../Categories/UIImageView+MNZCategory.m | 1 - .../UIKit/ErrorHandling/ErrorHandling.swift | 2 +- .../UIResponder+ErrorHandling.swift | 2 +- .../UIViewController+ErrorHandling.swift | 2 +- .../Categories/UITableView+MNZCategory.h | 1 - .../Categories/UITableView+MNZCategory.m | 1 - .../Categories/UITextField+MNZCategory.h | 1 - .../Categories/UITextField+MNZCategory.m | 1 - iMEGA/Utils/Categories/UIView+MNZCategory.h | 1 - iMEGA/Utils/Categories/UIView+MNZCategory.m | 1 - .../UIViewController+Additions.swift | 1 - .../Categories/UIViewController+MNZCategory.h | 1 - .../Categories/UIViewController+MNZCategory.m | 1 - .../ContextMenus/ContextMenuDataModel.swift | 1 - .../ContextMenuModel+Mapper.swift | 1 + iMEGA/Utils/CoreData/MEGACoreDataStack.h | 1 - iMEGA/Utils/CoreData/MEGAStore+Message.swift | 1 - iMEGA/Utils/CoreData/MEGAStore.m | 1 - .../CoreData/MOUploadTransfer+Mapper.swift | 1 - .../AppearancePreference+CoreDataClass.swift | 1 - ...earancePreference+CoreDataProperties.swift | 1 - ...udAppearancePreference+CoreDataClass.swift | 1 - ...earancePreference+CoreDataProperties.swift | 1 - .../MEGAStore+CloudAppearancePreference.swift | 1 - ...EGAStore+OfflineAppearancePreference.swift | 1 - .../Appearance/MOFolderLayout+CoreDataClass.h | 1 - .../Appearance/MOFolderLayout+CoreDataClass.m | 1 - .../MOFolderLayout+CoreDataProperties.h | 1 - .../MOFolderLayout+CoreDataProperties.m | 1 - .../MOOfflineFolderLayout+CoreDataClass.h | 1 - .../MOOfflineFolderLayout+CoreDataClass.m | 1 - ...MOOfflineFolderLayout+CoreDataProperties.h | 1 - ...MOOfflineFolderLayout+CoreDataProperties.m | 1 - ...neAppearancePreference+CoreDataClass.swift | 1 - ...earancePreference+CoreDataProperties.swift | 1 - ...OAssetUploadErrorPerLaunch+CoreDataClass.h | 1 - ...OAssetUploadErrorPerLaunch+CoreDataClass.m | 1 - ...tUploadErrorPerLaunch+CoreDataProperties.h | 1 - ...tUploadErrorPerLaunch+CoreDataProperties.m | 1 - ...MOAssetUploadErrorPerLogin+CoreDataClass.h | 1 - ...MOAssetUploadErrorPerLogin+CoreDataClass.m | 1 - ...etUploadErrorPerLogin+CoreDataProperties.h | 1 - ...etUploadErrorPerLogin+CoreDataProperties.m | 1 - .../MOAssetUploadErrorRecord+CoreDataClass.h | 1 - .../MOAssetUploadErrorRecord+CoreDataClass.m | 1 - ...ssetUploadErrorRecord+CoreDataProperties.h | 1 - ...ssetUploadErrorRecord+CoreDataProperties.m | 1 - ...OAssetUploadFileNameRecord+CoreDataClass.h | 1 - ...OAssetUploadFileNameRecord+CoreDataClass.m | 1 - ...tUploadFileNameRecord+CoreDataProperties.h | 1 - ...tUploadFileNameRecord+CoreDataProperties.m | 1 - .../MOAssetUploadRecord+CoreDataClass.h | 1 - .../MOAssetUploadRecord+CoreDataClass.m | 1 - .../MOAssetUploadRecord+CoreDataProperties.h | 1 - .../MOAssetUploadRecord+CoreDataProperties.m | 1 - .../ChatUploadTransfer+CoreDataClass.swift | 1 - ...hatUploadTransfer+CoreDataProperties.swift | 1 - .../MOChatDraft+CoreDataClass.h | 1 - .../MOChatDraft+CoreDataClass.m | 1 - .../MOChatDraft+CoreDataProperties.h | 1 - .../MOChatDraft+CoreDataProperties.m | 1 - .../MOMediaDestination+CoreDataClass.h | 1 - .../MOMediaDestination+CoreDataClass.m | 1 - .../MOMediaDestination+CoreDataProperties.h | 1 - .../MOMediaDestination+CoreDataProperties.m | 1 - .../ManagedObjects/MOMessage+CoreDataClass.h | 1 - .../ManagedObjects/MOMessage+CoreDataClass.m | 1 - .../MOMessage+CoreDataProperties.h | 1 - .../MOMessage+CoreDataProperties.m | 1 - .../MOUploadTransfer+CoreDataClass.h | 1 - .../MOUploadTransfer+CoreDataClass.m | 1 - .../MOUploadTransfer+CoreDataProperties.h | 1 - .../MOUploadTransfer+CoreDataProperties.m | 1 - .../User/MEGAStore+MOUser.swift | 1 - ...essWidgetFavouriteItem+CoreDataClass.swift | 1 - ...dgetFavouriteItem+CoreDataProperties.swift | 1 - ...AccessWidgetRecentItem+CoreDataClass.swift | 1 - ...sWidgetRecentItem+CoreDataProperties.swift | 1 - ...FileLinkActionViewControllerDelegate.swift | 13 +- ...eActionViewControllerGenericDelegate.swift | 8 +- .../AlbumNameUseCase+Additions.swift | 1 + ...veMediaToPhotosErrorEntity+Additions.swift | 1 + iMEGA/Utils/Headers/CallPeerAudio.h | 1 - iMEGA/Utils/Headers/CallPeerVideo.h | 1 - iMEGA/Utils/Headers/CallType.h | 1 - iMEGA/Utils/Headers/ChatImageUploadQuality.h | 1 - iMEGA/Utils/Headers/ChatRoomsType.h | 1 - iMEGA/Utils/Headers/ChatStatus.swift | 1 + iMEGA/Utils/Headers/ChatVideoUploadQuality.h | 1 - iMEGA/Utils/Headers/ContacLinkQRType.h | 1 - iMEGA/Utils/Headers/DisplayMode.h | 1 - iMEGA/Utils/Headers/JoinViewState.h | 1 - iMEGA/Utils/Headers/LinkOption.h | 1 - iMEGA/Utils/Headers/MegaNodeActionType.h | 1 - iMEGA/Utils/Headers/NodeSelectionType.swift | 1 - iMEGA/Utils/Headers/OnboardingType.h | 1 - iMEGA/Utils/Headers/OnboardingViewType.h | 1 - iMEGA/Utils/Headers/SendMode.h | 1 - iMEGA/Utils/Headers/ShareAttachmentType.h | 1 - iMEGA/Utils/Headers/SortingPreference.swift | 1 - iMEGA/Utils/Headers/ToolbarType.h | 1 - iMEGA/Utils/Headers/TransfersSelected.h | 1 - iMEGA/Utils/Headers/TransfersWidgetSelected.h | 1 - iMEGA/Utils/Headers/TwoFactorAuthentication.h | 1 - iMEGA/Utils/Headers/URLType.h | 1 - iMEGA/Utils/Headers/ViewModePreference.swift | 1 - iMEGA/Utils/Helper.m | 51 +- iMEGA/Utils/LiveText/LiveTextImageView.swift | 2 +- iMEGA/Utils/Logs/MEGALogMacros.h | 1 - iMEGA/Utils/Logs/MEGALogger.h | 1 - iMEGA/Utils/Logs/MEGALogger.m | 1 - iMEGA/Utils/MEGAConstants.h | 1 - iMEGA/Utils/MEGAConstants.m | 1 - iMEGA/Utils/MEGALinkManager+Additions.swift | 47 +- iMEGA/Utils/MEGALinkManager.h | 1 - iMEGA/Utils/MEGALinkManager.m | 92 +- iMEGA/Utils/MEGALocalNotificationManager.h | 1 - iMEGA/Utils/MEGALocalNotificationManager.m | 13 +- iMEGA/Utils/MEGANavigationController.m | 4 +- iMEGA/Utils/MEGAProcessAsset.h | 1 - iMEGA/Utils/MEGAProcessAsset.m | 17 +- .../MEGAReachabilityManager+Additions.swift | 1 - iMEGA/Utils/MEGAReachabilityManager.h | 1 - iMEGA/Utils/MEGAReachabilityManager.m | 11 +- iMEGA/Utils/MEGASDK/MEGASdkManager.m | 1 - .../Utils/Network/Models/ResponseModel.swift | 2 +- .../Services/Giphy/GiphyResponseModel.swift | 2 +- .../CameraUploadNodeAccess.swift | 7 +- .../MyChatFilesFolderNodeAccess.swift | 7 +- iMEGA/Utils/NodeAccess/NodeAccess.swift | 4 +- .../Utils/NodeAccess/NodeLoadOperation.swift | 4 +- iMEGA/Utils/NodeAssetsManager.swift | 2 +- .../OCWrapper/SaveNodeUseCaseOCWrapper.swift | 5 +- .../AlertModel+DevicePermissions.swift | 16 +- .../CustomModalModel+DevicePermissions.swift | 16 +- .../Cells/GenericHeaderFooterView.swift | 1 - .../Cells/Node/NodeCellViewModel.swift | 3 +- .../DynamicTypeComponentProtocol.swift | 1 - .../Shared Controls/MEGAAVViewController.h | 1 - .../Shared Controls/MEGAAVViewController.m | 13 +- .../MEGAImagePickerController.h | 1 - .../MEGAImagePickerController.m | 5 +- .../Snackbar/SnackBarPresenting.swift | 16 +- .../Snackbar/SnackBarRouter.swift | 4 +- .../Snackbar/SnackBarViewModel.swift | 8 +- .../Layer3/ExploreViewStyleFactory.swift | 1 + .../TableViewEmptyStateConfiguration.swift | 1 + .../TableView/TableViewProxy.swift | 2 +- .../AccountExpiredViewController.swift | 14 +- .../ActionSheetFactory.swift | 1 + .../BannerContainerViewController.swift | 1 - .../BannerContainerViewRouter.swift | 1 - .../BannerContainerScene/BannerType.swift | 1 - .../ActionSheetActions.swift | 3 +- .../NodeActionViewController.swift | 42 +- .../TransferActionViewController.swift | 3 +- .../CustomModalAlertViewController.h | 1 - .../CustomModalAlertViewController.m | 1 - ...tomModalAlertViewController+Business.swift | 1 + ...ustomModalAlertViewController+Config.swift | 3 +- ...tomModalAlertViewController+Contacts.swift | 4 +- ...odalAlertViewController+CookieDialog.swift | 1 + ...omModalAlertViewController+LaunchTab.swift | 2 + ...mModalAlertViewController+SharedItem.swift | 1 + ...stomModalAlertViewController+Storage.swift | 13 +- ...tomModalAlertViewController+Transfer.swift | 1 + ...ewController+TwoFactorAuthentication.swift | 12 +- ...lAlertViewController+UpgradeSecurity.swift | 1 + ...odalAlertViewController+UpgradeToPro.swift | 1 + ...lertViewcontroller+EnableKeyRotation.swift | 3 +- .../CustomModalAlertContactsRouter.swift | 1 - .../Routers/CustomModalAlertRouter.swift | 1 - .../CustomModalAlertStorageRouter.swift | 1 - .../PreviewDocumentViewController.m | 22 +- .../SearchInPdfViewController.h | 1 - .../SearchInPdfViewController.m | 1 - .../ViewControllers/MEGAQLPreviewController.h | 1 - .../ViewControllers/MEGAQLPreviewController.m | 1 - .../OnboardingView/OnboardingView.h | 1 - .../OnboardingView/OnboardingView.m | 31 +- .../OnboardingViewController.h | 1 - .../OnboardingViewController.m | 13 +- .../OverDiskQuotaViewController.swift | 1 + .../PasswordReminderViewController.h | 1 - .../PasswordReminderViewController.m | 25 +- .../TestPasswordViewController.h | 1 - .../TestPasswordViewController.m | 29 +- .../PasteImagePreviewView.swift | 1 + .../StorageFullModalAlertViewController.swift | 1 + .../CreateTextFileAlertViewController.swift | 2 + .../TextEditor/TextEditorViewController.swift | 1 + .../TextEditor/TextEditorViewModel.swift | 1 + .../TurnOnNotificationsModel.swift | 1 - .../TurnOnNotificationsViewModel.swift | 1 + .../VerifyEmailViewController.swift | 35 +- ...eSecureFingerprintFlagUIAlertAdapter.swift | 3 +- .../ViewRouters/ActionWarningViewRouter.swift | 2 + .../ViewRouters/SharedItemsViewRouter.swift | 1 - .../AwaitingEmailConfirmationView.h | 1 - .../AwaitingEmailConfirmationView.m | 1 - .../Shared Views/Views/BadgeButton.swift | 1 + .../Utils/Shared Views/Views/CopyableLabel.h | 1 - .../Utils/Shared Views/Views/CopyableLabel.m | 1 - .../EmptyState/EmptyStateView+Config.swift | 1 + .../Views/EmptyState/EmptyStateView.h | 1 - iMEGA/Utils/Shared Views/Views/InputView.h | 1 - iMEGA/Utils/Shared Views/Views/InputView.m | 9 +- .../Shared Views/Views/PaddingLabel.swift | 1 - .../Views/PasswordStrengthIndicatorView.h | 1 - .../Views/PasswordStrengthIndicatorView.m | 23 +- iMEGA/Utils/Shared Views/Views/PasswordView.h | 1 - iMEGA/Utils/Shared Views/Views/PasswordView.m | 11 +- .../Widget/QuickAccessWidgetManager.swift | 72 +- .../SharedItemsSearchOperation.swift | 6 +- .../PermissionAlertModel.swift | 44 + .../PermissionAlertViewModifier.swift | 64 + .../WarningView/WarningViewModel.swift | 2 + iMEGA/Utils/Tab.swift | 1 + iMEGA/Utils/TransferRecordDTO.swift | 1 - jenkinsfile/ios_automation_app_upload.groovy | 2 +- jenkinsfile/ios_build_status.groovy | 2 +- jenkinsfile/ios_submit_for_review.groovy | 2 +- jenkinsfile/ios_upload.groovy | 2 +- scripts/boot-simulators.sh | 3 +- scripts/check_translations.py | 6 +- transifex/iosTransifex.py | 16 +- 1587 files changed, 19287 insertions(+), 6875 deletions(-) create mode 100644 MEGA.xcodeproj/xcshareddata/xcschemes/SearchDemo.xcscheme delete mode 100644 MEGAData/Repository/Chat/ManageChatHistoryRepository.swift delete mode 100644 MEGAData/Repository/Node/NodeActionRepository.swift delete mode 100644 MEGAData/Repository/User/UserInviteRepository.swift create mode 100644 MEGAData/SDK/Model Mapping/Chat/Call/WaitingRoomEntity+Mapper.swift delete mode 100644 MEGAData/SDK/Model Mapping/User/UserAttributeEntity+Mapper.swift delete mode 100644 MEGAData/SDK/RequestDelegate/InviteRequestDelegate.swift delete mode 100644 MEGAData/SDK/RequestDelegate/MEGAResultMappingRequestDelegate.swift create mode 100644 MEGADataTests/Repos/DownloadFileRepositoryTests.swift create mode 100644 MEGAUnitTests/AudioPlayer/Scenes/AudioPlayerScenes/AudioPlayerViewRouterNodeActionAdapterTests.swift create mode 100644 MEGAUnitTests/Home/Search/HomeSearchResultsProvidingTests.swift delete mode 100644 MEGAUnitTests/Meeting/Mocks/MockUserInviteUseCase.swift create mode 100644 MEGAUnitTests/Mocks/MockTransferWidgetResponder.swift rename MEGAUnitTests/Permissions/{ => AlertRouter}/AlertModel+Equatable.swift (100%) rename MEGAUnitTests/Permissions/{ => AlertRouter}/CustomModalModel+Equatable.swift (100%) rename MEGAUnitTests/Permissions/{ => AlertRouter}/PermissionAlertRouterTests.swift (100%) create mode 100644 MEGAUnitTests/Permissions/AlertViewModifier/PermissionAlertModelTests.swift create mode 100644 MEGAUnitTests/Widget/QuickAccessWidgetManagerTests.swift create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Ads/AdsFlagEntity.swift create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Ads/AdsSlotEntity.swift create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Album/AlbumMetaDataEntity.swift create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Banner/BannerEntity.swift rename {iMEGA/Home/UseCase/Domain/Error => Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Banner}/BannerErrorEntity.swift (64%) create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/WaitingRoomEntity.swift delete mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanTermEntity.swift rename {MEGADomain/Entity/Error => Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User}/UserImageLoadErrorEntity.swift (71%) create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Ads/AdsRepositoryProtocol.swift create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Banner/BannerRepositoryProtocol.swift create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/WaitingRoomRepositoryProtocol.swift rename {MEGADomain => Modules/Domain/MEGADomain/Sources/MEGADomain}/RepositoryProtocol/User/UserImageRepositoryProtocol.swift (59%) create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Ads/AdsUseCase.swift rename {iMEGA/Home => Modules/Domain/MEGADomain/Sources/MEGADomain}/UseCase/Banner/UserBannerUseCase.swift (57%) create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/WaitingRoomUseCase.swift create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockAdsRepository.swift create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockChatRoomRepository.swift create mode 100644 Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockWaitingRoomUseCase.swift create mode 100644 Modules/Domain/MEGADomain/Tests/MEGADomainTests/AdsUseCaseTests.swift create mode 100644 Modules/Domain/MEGADomain/Tests/MEGADomainTests/ChatRoomUseCaseTests.swift create mode 100644 Modules/Domain/MEGAIntentDomain/.gitignore create mode 100644 Modules/Domain/MEGAIntentDomain/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Modules/Domain/MEGAIntentDomain/Package.swift create mode 100644 Modules/Domain/MEGAIntentDomain/README.md create mode 100644 Modules/Domain/MEGAIntentDomain/Sources/MEGAIntentDomain/UseCase/IntentPersonUseCase.swift create mode 100644 Modules/Domain/MEGAIntentDomain/Tests/MEGAIntentDomainTests/IntentPersonUseCaseTests.swift create mode 100644 Modules/Features/Search/Sources/Search/Infrastructure/AuxilaryTypes.swift create mode 100644 Modules/Features/Search/Sources/Search/Infrastructure/SearchBridge.swift create mode 100644 Modules/Features/Search/Sources/Search/Infrastructure/SearchChipsEntity.swift create mode 100644 Modules/Features/Search/Sources/Search/Infrastructure/SearchQueryEntity.swift create mode 100644 Modules/Features/Search/Sources/Search/Infrastructure/SearchResult.swift create mode 100644 Modules/Features/Search/Sources/Search/Infrastructure/SearchResultsEntity.swift create mode 100644 Modules/Features/Search/Sources/Search/Infrastructure/SearchResultsProviding.swift create mode 100644 Modules/Features/Search/Sources/Search/NonProductTestSearchrResultsProvider.swift delete mode 100644 Modules/Features/Search/Sources/Search/Search.swift create mode 100644 Modules/Features/Search/Sources/Search/UI/SearchResultRowView.swift create mode 100644 Modules/Features/Search/Sources/Search/UI/SearchResultRowViewModel.swift create mode 100644 Modules/Features/Search/Sources/Search/UI/SearchResultsView.swift create mode 100644 Modules/Features/Search/Sources/Search/UI/SearchResultsViewModel.swift create mode 100644 Modules/Features/Search/Sources/SearchMock/MockSearchResultsProviding.swift create mode 100644 Modules/Features/Search/Tests/SearchTests/SearchResultsViewModelTests.swift delete mode 100644 Modules/Features/Search/Tests/SearchTests/SearchTests.swift create mode 100644 Modules/Features/Search/Tests/SearchTests/XCTestHelpers.swift create mode 100644 Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/AVAudioSession/AVAudioSession+Additions.swift rename Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/CNContact/{CNConact+Additions.swift => CNContact+Additions.swift} (68%) rename Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/{Extensions => DispatchQueue}/DispatchQueue+Additions.swift (100%) create mode 100644 Modules/Infrastracture/MEGAFoundation/Tests/MEGAFoundationTests/CNContact/CNContactTests.swift create mode 100644 Modules/Localization/MEGAL10n/.gitignore create mode 100644 Modules/Localization/MEGAL10n/.swiftpm/xcode/xcshareddata/xcschemes/MEGALocalization.xcscheme create mode 100644 Modules/Localization/MEGAL10n/Package.swift create mode 100644 Modules/Localization/MEGAL10n/README.md create mode 100644 Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.h create mode 100644 Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.m create mode 100644 Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.swift rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/Base.lproj/Localizable.strings (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/Base.lproj/Localizable.stringsdict (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/ar.lproj/Localizable.strings (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/ar.lproj/Localizable.stringsdict (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/de.lproj/Localizable.strings (97%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/de.lproj/Localizable.stringsdict (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/en.lproj/Localizable.strings (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/en.lproj/Localizable.stringsdict (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/es.lproj/Localizable.strings (97%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/es.lproj/Localizable.stringsdict (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/fr.lproj/Localizable.strings (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/fr.lproj/Localizable.stringsdict (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/id.lproj/Localizable.strings (97%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/id.lproj/Localizable.stringsdict (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/it.lproj/Localizable.strings (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/it.lproj/Localizable.stringsdict (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/ja.lproj/Localizable.strings (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/ja.lproj/Localizable.stringsdict (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/ko.lproj/Localizable.strings (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/ko.lproj/Localizable.stringsdict (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/nl.lproj/Localizable.strings (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/nl.lproj/Localizable.stringsdict (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/pl.lproj/Localizable.strings (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/pl.lproj/Localizable.stringsdict (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/pt.lproj/Localizable.strings (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/pt.lproj/Localizable.stringsdict (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/ro.lproj/Localizable.strings (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/ro.lproj/Localizable.stringsdict (95%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/ru.lproj/Localizable.strings (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/ru.lproj/Localizable.stringsdict (99%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/th.lproj/Localizable.strings (95%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/th.lproj/Localizable.stringsdict (97%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/vi.lproj/Localizable.strings (97%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/vi.lproj/Localizable.stringsdict (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/zh-Hans.lproj/Localizable.strings (97%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/zh-Hans.lproj/Localizable.stringsdict (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/zh-Hant.lproj/Localizable.strings (98%) rename {iMEGA/Languages => Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources}/zh-Hant.lproj/Localizable.stringsdict (98%) create mode 100644 Modules/Localization/MEGAL10n/Sources/MEGAL10n/Strings+Generated.swift create mode 100644 Modules/Localization/MEGAL10n/SwiftGen/swiftgen.yml create mode 100644 Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterListContainer/DeviceCenterListContainerView.swift create mode 100644 Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterAction.swift create mode 100644 Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterItemType.swift create mode 100644 Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenterMocks/Models/DeviceCenterAction+Test.swift create mode 100644 Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/ActionSheetIcons/cloudDriveFolder.imageset/Contents.json create mode 100644 Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/ActionSheetIcons/cloudDriveFolder.imageset/Group 1.pdf create mode 100644 Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/ActionSheetIcons/cloudDriveFolder.imageset/Group.pdf rename Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/Filetypes/{folder_image.imageset => folder_camera.imageset}/Contents.json (100%) rename Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/Filetypes/{folder_image.imageset => folder_camera.imageset}/folder_camera_uploads@2x.png (100%) rename Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/Filetypes/{folder_image.imageset => folder_camera.imageset}/folder_camera_uploads@3x.png (100%) rename Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/Filetypes/{folder_image.imageset => folder_camera.imageset}/folder_camera_uploads_dark@2x.png (100%) rename Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/Filetypes/{folder_image.imageset => folder_camera.imageset}/folder_camera_uploads_darkDark@3x.png (100%) rename Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/MyAccount/{scanQRCode.imageset => account_scanQRCode.imageset}/Contents.json (100%) rename Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/MyAccount/{scanQRCode.imageset => account_scanQRCode.imageset}/scanQRCode.pdf (100%) rename Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/MyAccount/{scanQRCode.imageset => account_scanQRCode.imageset}/scanQRCodeDark.pdf (100%) rename Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/WarningStorageAlmostFull/{storage_full.imageset => warning_storage_full.imageset}/Contents.json (100%) rename Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/WarningStorageAlmostFull/{storage_full.imageset => warning_storage_full.imageset}/storage_full.pdf (100%) create mode 100644 Modules/Presentation/MEGAPresentation/Sources/MEGAPresentation/Extensions/Int+Analytics.swift create mode 100644 Modules/Repository/ChatRepo/Sources/ChatRepo/Repository/Chat/ManageChatHistoryRepository.swift create mode 100644 Modules/Repository/ChatRepo/Sources/ChatRepo/Repository/Chat/WaitingRoomRepository.swift rename {MEGAData/SDK/Model Mapping/Chat/Call => Modules/Repository/MEGARepo/Sources/MEGARepo/Mappers}/CameraPositionEntity+Mapper.swift (100%) rename {MEGAData/Repository/PhotosLibrary => Modules/Repository/MEGARepo/Sources/MEGARepo/Photos}/PhotosLibraryRepository.swift (67%) rename {MEGAData => Modules/Repository/MEGARepo/Sources/MEGARepo}/Repository/AudioVideo/CaptureDeviceRepository.swift (50%) create mode 100644 Modules/Repository/MEGARepo/Sources/MEGARepo/Repository/Contacts/ContactsRepository.swift rename Modules/Repository/MEGARepo/Sources/MEGARepo/{ => Repository}/Filesystem/FileSystemRepository.swift (100%) rename Modules/Repository/MEGARepo/Sources/MEGARepo/{ => Repository}/Preference/PreferenceRepository.swift (100%) rename Modules/Repository/{MEGASDKRepo/Tests/MEGASDKRepoTests => MEGARepo/Tests/MEGARepoTests}/ContactsRepositoryTests.swift (95%) create mode 100644 Modules/Repository/MEGASDKRepo/.swiftpm/xcode/xcshareddata/xcschemes/MEGASDKRepo.xcscheme rename {iMEGA/My Account/Upgrade Account/Data Model => Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Account}/AccountPlanEntity+Mapper.swift (79%) create mode 100644 Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Ads/AdsFlagEntity+Mapper.swift create mode 100644 Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Ads/AdsRepository.swift create mode 100644 Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Banner/BannerRepository.swift delete mode 100644 Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Contact/ContactsRepository.swift rename {MEGAData => Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo}/Repository/Favourites/FavouriteNodesRepository.swift (78%) create mode 100644 Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeActionRepository.swift rename {MEGAData => Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo}/Repository/Node/NodeAttributeRepository.swift (57%) rename {MEGAData => Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo}/Repository/Node/NodeFavouriteActionRepository.swift (83%) rename {MEGAData => Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo}/Repository/Node/NodeUpdateRepository.swift (68%) rename {MEGAData => Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo}/Repository/Node/RubbishBinRepository.swift (73%) rename {MEGAData => Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo}/Repository/User/UserAttributeRepository.swift (77%) rename {MEGAData => Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo}/Repository/User/UserImageRepository.swift (69%) create mode 100644 Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/User/UserInviteRepository.swift rename iMEGA/Home/Repository/EntityMapping/MEGABanner.swift => Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/SDK/ModelMapping/Banner/BannerEntity+Mapper.swift (95%) rename iMEGA/Home/Repository/EntityMapping/MEGABannerList.swift => Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/SDK/ModelMapping/Banner/BannerListEntity+Mapper.swift (72%) create mode 100644 Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/AdsRepositoryTests.swift rename {MEGADataTests/Repos => Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests}/NodeAttributeRepositoryTests.swift (98%) rename {MEGADataTests/Repos => Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests}/NodeUpdateRepositoryTests.swift (98%) rename {MEGADataTests/Repos => Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests}/RubbishBinRepositoryTests.swift (93%) create mode 100644 Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/ActionSheet/ActionSheetHeaderView.swift create mode 100644 Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/ActionSheet/ActionSheetView.swift create mode 100644 Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/Text/FocusableTextFieldView.swift create mode 100644 SearchDemo/ContentView.swift create mode 100644 SearchDemo/SearchDemoApp.swift create mode 100644 iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewRouterNodeActionAdapter.swift create mode 100644 iMEGA/ChatAttachedNodesViewController+Additions.swift delete mode 100644 iMEGA/Extensions/MEGAIntent/StartCall/IntentPersonProvider.swift delete mode 100644 iMEGA/Extensions/MEGAWidget/Assets.xcassets/Colors/#00A886.colorset/Contents.json delete mode 100644 iMEGA/Home/Repository/SDK/Banner/BannerRepository.swift create mode 100644 iMEGA/Home/Scenes/HomeSearch/Provider/HomeSearchResultsProviding.swift create mode 100644 iMEGA/Home/Scenes/SearchResultsBridge.swift delete mode 100644 iMEGA/Home/UseCase/Domain/BannerEntity.swift create mode 100644 iMEGA/Home/UseCase/Node/NodeDetails/MockNodeDetailsUseCase.swift rename iMEGA/Home/UseCase/Node/{ => NodeDetails}/NodeDetailsUseCase.swift (100%) create mode 100644 iMEGA/Home/UseCase/Node/Search/SearchFileUseCase/MockSearchFileUseCase.swift rename iMEGA/Home/UseCase/Node/Search/{ => SearchFileUseCase}/SearchFileUseCase.swift (100%) create mode 100644 iMEGA/Media Consumption/Album Library/Extensions/AlbumEntity+Analytics.swift delete mode 100644 iMEGA/My Account/Contacts/ContactsPicker/ContactsPicker.storyboard delete mode 100644 iMEGA/My Account/Contacts/ContactsPicker/ContactsPickerViewController.swift delete mode 100644 iMEGA/My Account/Contacts/ContactsPicker/DeviceContactTableViewCell.swift delete mode 100644 iMEGA/My Account/Contacts/ContactsPicker/DeviceContactsManager.swift rename iMEGA/{Utils/Categories => My Account/Login}/MainTabBarController+Additions.swift (66%) create mode 100644 iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase+PromotedPlan.swift create mode 100644 iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+SnackBarPresenting.swift delete mode 100644 iMEGA/ScheduleMeetingScene/Views/TextFieldView.swift create mode 100644 iMEGA/Utils/SwiftUI/PermissionAlertViewModifier/PermissionAlertModel.swift create mode 100644 iMEGA/Utils/SwiftUI/PermissionAlertViewModifier/PermissionAlertViewModifier.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index f5cf2755d2..175c7679bb 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -37,6 +37,11 @@ custom_rules: regex: 'MEGAChatResultRequestDelegate' name: "Disallow MEGAChatResultRequestDelegate" message: "'MEGAChatResultRequestDelegate' is deprecated, use 'ChatRequestDelegate' instead" + + avoid_empty_lines_at_the_top_of_the_file: + regex: '\A[\r\n]+' + name: "Avoid empty lines at the top of the file" + message: "No empty lines are allowed at the top of the file" # Currently we use `only_rules` so that we can validate each and every default rule. # Once everything has been set, we can start using the `disabled_rules` and `opt_in_rules`. @@ -174,7 +179,8 @@ included: # Paths to include during linting. `--path` is ignored if present. - Modules excluded: # Paths to ignore during linting. Takes precedence over `included`. - - iMEGA/SwiftGen + - Modules/Localization/MEGAL10n/Sources/MEGAL10n/Strings+Generated.swift + - Modules/Localization/MEGAL10n/SwiftGen - SwiftGen - transifex - Xcode Templates diff --git a/MEGA.xcodeproj/project.pbxproj b/MEGA.xcodeproj/project.pbxproj index 578b9bc638..3d06c46faa 100644 --- a/MEGA.xcodeproj/project.pbxproj +++ b/MEGA.xcodeproj/project.pbxproj @@ -21,12 +21,11 @@ 0DA5BCB825BA403800530F70 /* DefaultTabTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA5BCB725BA403800530F70 /* DefaultTabTableViewController.swift */; }; 0E10BD602A7231EE00AE2980 /* AlbumNameUseCase+AdditionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E10BD5F2A7231EE00AE2980 /* AlbumNameUseCase+AdditionsTests.swift */; }; 0E1904642A92FF410048354A /* EmptyAlbumView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1904632A92FF410048354A /* EmptyAlbumView.swift */; }; + 0E2363B52A9EDDCC0019BBBE /* AlbumEntity+Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2363B42A9EDDCC0019BBBE /* AlbumEntity+Analytics.swift */; }; 0E2A4EE829D4FDFE00EB6ECE /* PhotoLibraryPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2A4EE729D4FDFE00EB6ECE /* PhotoLibraryPublisherTests.swift */; }; 0E2FD9DD2A258C3B00FB75F1 /* GetAlbumsLinkViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2FD9DC2A258C3B00FB75F1 /* GetAlbumsLinkViewModelTests.swift */; }; 0E2FEAA82A7B44AC00D237C6 /* GetLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2FEAA62A7B44AC00D237C6 /* GetLinkView.swift */; }; 0E2FEAAA2A7B463700D237C6 /* GetLinkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2FEAA92A7B463600D237C6 /* GetLinkRouter.swift */; }; - 0E32FFAA293E817A004BB11D /* NodeUpdateRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E32FFA9293E8179004BB11D /* NodeUpdateRepositoryTests.swift */; }; - 0E32FFB2293E896A004BB11D /* NodeUpdateRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E32FFB1293E896A004BB11D /* NodeUpdateRepository.swift */; }; 0E3B1A352A1D910B00EC5859 /* GetLinkAlbumInfoCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B1A342A1D910B00EC5859 /* GetLinkAlbumInfoCellViewModel.swift */; }; 0E3B1A392A1D919B00EC5859 /* GetLinkAlbumInfoCellViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B1A382A1D919B00EC5859 /* GetLinkAlbumInfoCellViewModelTests.swift */; }; 0E3B1A482A1DB9FD00EC5859 /* GetLinkSwitchOptionCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B1A472A1DB9FD00EC5859 /* GetLinkSwitchOptionCellViewModel.swift */; }; @@ -56,6 +55,7 @@ 0ECDCD502A1DDEEA00FBEFE7 /* GetLinkSwitchCellViewConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECDCD4F2A1DDEEA00FBEFE7 /* GetLinkSwitchCellViewConfiguration.swift */; }; 0EDE0DDB2A20587E0006A9F1 /* GetAlbumsLinksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE0DDA2A20587E0006A9F1 /* GetAlbumsLinksViewModel.swift */; }; 0EDE0DDD2A2058DE0006A9F1 /* ShareAlbumsLinkInitialSections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE0DDC2A2058DE0006A9F1 /* ShareAlbumsLinkInitialSections.swift */; }; + 0EE990942AA55D9300D43714 /* ChatAttachedNodesViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE990932AA55D9300D43714 /* ChatAttachedNodesViewController+Additions.swift */; }; 12084048286120020079127B /* SearchOperation+Initialize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12084047286120020079127B /* SearchOperation+Initialize.swift */; }; 1208CEC527FA49EB00F514D4 /* PhotoCellVideoDurationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1208CEC427FA49EB00F514D4 /* PhotoCellVideoDurationViewModifier.swift */; }; 122977DE27DA976F0048F381 /* MediaDiscoveryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 122977DD27DA976F0048F381 /* MediaDiscoveryViewModel.swift */; }; @@ -143,6 +143,8 @@ 246013122A459166007EF5ED /* AdvancedViewRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 246013112A459166007EF5ED /* AdvancedViewRouterTests.swift */; }; 2461E7042A8CC90600A8FFA6 /* StreamingInfoRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2461E7032A8CC90600A8FFA6 /* StreamingInfoRepositoryTests.swift */; }; 246A311F2A45709200BF8DDB /* AdvancedTableViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 246A311E2A45709200BF8DDB /* AdvancedTableViewController+Additions.swift */; }; + 246ACEB52AA2D9C5000BD2A8 /* AudioPlayerViewRouterNodeActionAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 246ACEB42AA2D9C5000BD2A8 /* AudioPlayerViewRouterNodeActionAdapterTests.swift */; }; + 246ACEB92AA2DAEE000BD2A8 /* AudioPlayerViewRouterNodeActionAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 246ACEB82AA2DAEE000BD2A8 /* AudioPlayerViewRouterNodeActionAdapter.swift */; }; 247FC1BE2A4F1C3700CC8518 /* QASettingsRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 247FC1BD2A4F1C3700CC8518 /* QASettingsRouterTests.swift */; }; 2488EBC32A13B503005F910E /* NodeActionViewControllerGenericDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2488EBC22A13B503005F910E /* NodeActionViewControllerGenericDelegateTests.swift */; }; 248A031B2A79026800F183A2 /* MEGAAVViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 248A031A2A79026800F183A2 /* MEGAAVViewControllerTests.swift */; }; @@ -162,11 +164,17 @@ 2546EE052A4149B300CBB82E /* AVPlayerManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2546EE042A4149B300CBB82E /* AVPlayerManagerTests.swift */; }; 2562E8C52A37E73E0031D286 /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2562E8C42A37E73E0031D286 /* ProfileViewModel.swift */; }; 2562E8C72A3921CE0031D286 /* ProfileTableViewDiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2562E8C62A3921CE0031D286 /* ProfileTableViewDiffableDataSource.swift */; }; + 2592C2A22AA00D3100F902C8 /* MEGAPhotoBrowserViewController+SnackBarPresenting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2592C2A12AA00D3100F902C8 /* MEGAPhotoBrowserViewController+SnackBarPresenting.swift */; }; 259FA4032A3A949F00C038B2 /* ProfileViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259FA4022A3A949F00C038B2 /* ProfileViewModelTests.swift */; }; 259FA4082A3A9CEB00C038B2 /* ProfileSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259FA4052A3A9C9F00C038B2 /* ProfileSection.swift */; }; + 25D840692A9EE0E50026D288 /* PermissionAlertViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25D840682A9EE0E50026D288 /* PermissionAlertViewModifier.swift */; }; + 25D8406C2A9EE2860026D288 /* PermissionAlertModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25D8406B2A9EE2860026D288 /* PermissionAlertModel.swift */; }; + 25D840712A9EE9330026D288 /* PermissionAlertModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25D840702A9EE9330026D288 /* PermissionAlertModelTests.swift */; }; 25DB82D32A3C07D100207E72 /* ProfileTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25DB82D22A3C07D100207E72 /* ProfileTableViewDataSource.swift */; }; 25E2717D2A3FE96800DDB678 /* AVPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25E2717C2A3FE96800DDB678 /* AVPlayerManager.swift */; }; 25E2717F2A40099800DDB678 /* MEGAAVViewController+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25E2717E2A40099800DDB678 /* MEGAAVViewController+Combine.swift */; }; + 25ED566A2A98699E0063864F /* MockTransferWidgetResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25ED56692A98699E0063864F /* MockTransferWidgetResponder.swift */; }; + 25ED56812A9D867D0063864F /* DownloadFileRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25ED56802A9D867D0063864F /* DownloadFileRepositoryTests.swift */; }; 271FC4212A27EA4400E35924 /* ElementStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271FC4202A27EA4400E35924 /* ElementStore.swift */; }; 274514C22A2F5BDE00BFFB2F /* ElementStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274514C12A2F5BDE00BFFB2F /* ElementStoreTests.swift */; }; 27AFAC542A53734500C0D6A0 /* FileExtensionOCWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27AFAC532A53734500C0D6A0 /* FileExtensionOCWrapper.swift */; }; @@ -263,7 +271,6 @@ 3232AD012900F7A500FF7416 /* SlideshowDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232AD002900F7A500FF7416 /* SlideshowDataSourceTests.swift */; }; 3232ADC3290F95C900FF7416 /* FilesSearchRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3232ADC2290F95C900FF7416 /* FilesSearchRepository.swift */; }; 3234143829F0F6A500A06EFE /* PhotoLibraryContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3234143729F0F6A500A06EFE /* PhotoLibraryContentConfiguration.swift */; }; - 3241349729FB712B006E5310 /* MEGASdk in Frameworks */ = {isa = PBXBuildFile; productRef = 3241349629FB712B006E5310 /* MEGASdk */; }; 32473EEB2A1E16F60020012C /* SharedLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32473EEA2A1E16F60020012C /* SharedLinkView.swift */; }; 3256C38929A63A74001B0280 /* AlbumSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3256C38829A63A74001B0280 /* AlbumSelection.swift */; }; 3256C39529A8656C001B0280 /* AlbumSelectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3256C39429A8656C001B0280 /* AlbumSelectionTests.swift */; }; @@ -341,7 +348,6 @@ 415226981A692ECC00EC7BB6 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 415226831A692ECC00EC7BB6 /* Settings.storyboard */; }; 4152269A1A692ECC00EC7BB6 /* Helper.m in Sources */ = {isa = PBXBuildFile; fileRef = 415226881A692ECC00EC7BB6 /* Helper.m */; }; 4152269B1A692ECC00EC7BB6 /* MEGASdkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4152268A1A692ECC00EC7BB6 /* MEGASdkManager.m */; }; - 4152273F1A692F8D00EC7BB6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 415227411A692F8D00EC7BB6 /* Localizable.strings */; }; 4156C80C1AD7E79A00F5E818 /* LTHKeychainUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 4156C8091AD7E79A00F5E818 /* LTHKeychainUtils.m */; }; 4156C80D1AD7E79A00F5E818 /* LTHPasscodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4156C80B1AD7E79A00F5E818 /* LTHPasscodeViewController.m */; }; 415DCF241BF48EFA00914A1E /* ContactRequestsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 415DCF231BF48EFA00914A1E /* ContactRequestsViewController.m */; }; @@ -524,7 +530,6 @@ 5BF5388423743AD80023F2E4 /* ExtensionAppearanceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF538822374394F0023F2E4 /* ExtensionAppearanceManager.swift */; }; 5BFA346B1FAB4169005BFC4E /* MEGARemoveRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BFA34691FAB4168005BFC4E /* MEGARemoveRequestDelegate.m */; }; 5BFA346E1FAB6282005BFC4E /* MEGAMoveRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BFA346D1FAB6282005BFC4E /* MEGAMoveRequestDelegate.m */; }; - 5BFA9630252E1119003653EB /* ManageChatHistoryRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFA962F252E1119003653EB /* ManageChatHistoryRepository.swift */; }; 5D01995724AD64C1006ED2A0 /* InterfaceStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D01995624AD64C1006ED2A0 /* InterfaceStyle.swift */; }; 5D038F082511D5AF00DAA2E6 /* HomeSearchResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D038F062511D5AF00DAA2E6 /* HomeSearchResultViewController.swift */; }; 5D038F092511D5AF00DAA2E6 /* HomeSearchResultViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5D038F072511D5AF00DAA2E6 /* HomeSearchResultViewController.xib */; }; @@ -582,7 +587,6 @@ 5D568209252FEA9F00FE7F9C /* SortingOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D568208252FEA9F00FE7F9C /* SortingOrder.swift */; }; 5D56820B252FEC9800FE7F9C /* Reader+GroupingSorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D56820A252FEC9800FE7F9C /* Reader+GroupingSorting.swift */; }; 5D5DE4F82511F88900F2B4FD /* SearchFileUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5DE4F72511F88900F2B4FD /* SearchFileUseCase.swift */; }; - 5D609288255BA0AE005FA3E4 /* NodeFavouriteActionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D609287255BA0AE005FA3E4 /* NodeFavouriteActionRepository.swift */; }; 5D609290255BC2B6005FA3E4 /* SDKError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D60928F255BC2B6005FA3E4 /* SDKError.swift */; }; 5D609298255D5D40005FA3E4 /* NodeLabelActionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D609297255D5D40005FA3E4 /* NodeLabelActionRepository.swift */; }; 5D6092A6255D609F005FA3E4 /* NodeLabelActionUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D6092A5255D609F005FA3E4 /* NodeLabelActionUseCase.swift */; }; @@ -613,13 +617,7 @@ 5D7D53472551263C00D3582E /* TextFieldStyleFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D7D53462551263C00D3582E /* TextFieldStyleFactory.swift */; }; 5D7D6B9E2491F79E00ADBDEE /* OverDiskQuotaService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D7D6B9D2491F79E00ADBDEE /* OverDiskQuotaService.swift */; }; 5D816FC62564C08E0067D22F /* SlideIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D816FC52564C08E0067D22F /* SlideIndicatorView.swift */; }; - 5D8303BC256262350082CA18 /* BannerRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8303BB256262350082CA18 /* BannerRepository.swift */; }; 5D8303D2256266A80082CA18 /* GeneralDomainError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8303D1256266A80082CA18 /* GeneralDomainError.swift */; }; - 5D8303E7256270800082CA18 /* BannerEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8303E6256270800082CA18 /* BannerEntity.swift */; }; - 5D8303F5256367D60082CA18 /* BannerErrorEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8303F4256367D60082CA18 /* BannerErrorEntity.swift */; }; - 5D83041225638DC40082CA18 /* MEGABannerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D83041125638DC40082CA18 /* MEGABannerList.swift */; }; - 5D83042025638DE70082CA18 /* MEGABanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D83041F25638DE70082CA18 /* MEGABanner.swift */; }; - 5D83042F256392370082CA18 /* UserBannerUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D83042E256392370082CA18 /* UserBannerUseCase.swift */; }; 5D876543252CAAE200E6CB91 /* TableDataSourceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D876542252CAAE200E6CB91 /* TableDataSourceConfiguration.swift */; }; 5D8A76F024FCD0C900C0610C /* MEGANotificationUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8A76ED24FCD0C800C0610C /* MEGANotificationUseCase.swift */; }; 5D8A76F124FCD0C900C0610C /* MEGAAvatarGeneratingUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8A76EE24FCD0C900C0610C /* MEGAAvatarGeneratingUseCase.swift */; }; @@ -667,7 +665,6 @@ 5DEFE99424A1AE0E00DFF256 /* MarkedStringParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DEFE99324A1AE0E00DFF256 /* MarkedStringParser.swift */; }; 5DF016812499BD60005A121D /* MEGAPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF016802499BD60005A121D /* MEGAPlan.swift */; }; 5DF016832499BD7B005A121D /* MEGAPlanService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF016822499BD7B005A121D /* MEGAPlanService.swift */; }; - 5DF0E3D12563A8540052CC95 /* MEGAResultMappingRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF0E3CF2563A8540052CC95 /* MEGAResultMappingRequestDelegate.swift */; }; 5DF0E3D22563A8540052CC95 /* MEGAResultRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF0E3D02563A8540052CC95 /* MEGAResultRequestDelegate.swift */; }; 5DF249DD24F37589003FED76 /* FileUploadingRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF249DC24F37589003FED76 /* FileUploadingRouter.swift */; }; 5DF249DF24F381C8003FED76 /* DispatchFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF249DE24F381C8003FED76 /* DispatchFunction.swift */; }; @@ -757,7 +754,6 @@ 77B3227F20A436D60037FA89 /* NSURL+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 77B3227E20A436D60037FA89 /* NSURL+MNZCategory.m */; }; 77B3AE0F215D0588008E889A /* InitialLaunchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 77B3AE0E215D0588008E889A /* InitialLaunchViewController.m */; }; 77B9FEE723C604260002D0CD /* MEGAChatBaseRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A802B1F91FAB293700AC8BB0 /* MEGAChatBaseRequestDelegate.m */; }; - 77BE2FEF24000CB600514DB8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 415227411A692F8D00EC7BB6 /* Localizable.strings */; }; 77BE2FFB24084E9600514DB8 /* MEGAFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B695E57F23275A4E00D12B9E /* MEGAFunctions.swift */; }; 77C2E07C24BCBB9C00B4894A /* ActionSheetActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 837B9BFA246D38E800D409B4 /* ActionSheetActions.swift */; }; 77C2E07D24BCBC0100B4894A /* ActionSheetCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FCE04A7241B019800EE7842 /* ActionSheetCell.swift */; }; @@ -783,7 +779,6 @@ 77F95CF320E3E60A00F42D9C /* MEGAChatAttachNodeRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B222A73205A9CD40083D433 /* MEGAChatAttachNodeRequestDelegate.m */; }; 77FCB86523FD718A009E8032 /* NSString+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 41BADDAC1A8E1659008408B3 /* NSString+MNZCategory.m */; }; 830B548A2178845300BE1E1F /* CloudDriveTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 830B54892178845300BE1E1F /* CloudDriveTableViewController.m */; }; - 830C268125DFC78D00E9B56E /* FavouriteNodesRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830C268025DFC78D00E9B56E /* FavouriteNodesRepository.swift */; }; 830C28BB25E68FA700E9B56E /* QuickAccessWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830C28B125E68F8400E9B56E /* QuickAccessWidgetView.swift */; }; 830C28D625E68FC700E9B56E /* DetailItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830C28CC25E68FC200E9B56E /* DetailItemView.swift */; }; 830C28E025E6900200E9B56E /* GridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830C28DF25E6900200E9B56E /* GridView.swift */; }; @@ -797,7 +792,6 @@ 8313914F29351A4400E20170 /* ChatRoomParticipantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8313914E29351A4400E20170 /* ChatRoomParticipantView.swift */; }; 8313915129351A8600E20170 /* ChatRoomParticipantsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8313915029351A8600E20170 /* ChatRoomParticipantsListViewModel.swift */; }; 8313915329351AA300E20170 /* ChatRoomParticipantViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8313915229351AA300E20170 /* ChatRoomParticipantViewModel.swift */; }; - 8314150E256FFF5300D48BE3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 415227411A692F8D00EC7BB6 /* Localizable.strings */; }; 83143B722604AB380033161A /* CallLocalVideoUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83143B712604AB380033161A /* CallLocalVideoUseCase.swift */; }; 8315F6132A445E1600C98BCE /* ChatSourceEntity+Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8315F6122A445E1600C98BCE /* ChatSourceEntity+Mapper.swift */; }; 8318CE8E2562DA0600C97779 /* ShortcutsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8318CE832562D6DA00C97779 /* ShortcutsWidget.swift */; }; @@ -871,8 +865,6 @@ 8348A8DA2901909400B66463 /* ChatRoomActiveCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8348A8D92901909400B66463 /* ChatRoomActiveCallView.swift */; }; 834AFD1D2492567A005DF37C /* MEGAChatPeerList+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834AFD1C2492567A005DF37C /* MEGAChatPeerList+Additions.swift */; }; 834AFD1E2492569E005DF37C /* MEGAChatPeerList+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834AFD1C2492567A005DF37C /* MEGAChatPeerList+Additions.swift */; }; - 834AFD232498E925005DF37C /* ContactsPicker.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 834AFD222498E925005DF37C /* ContactsPicker.storyboard */; }; - 834AFD252498F637005DF37C /* DeviceContactTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834AFD242498F637005DF37C /* DeviceContactTableViewCell.swift */; }; 834D7DA02926699500634B66 /* LeaveChatButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834D7D9F2926699500634B66 /* LeaveChatButtonView.swift */; }; 834D865C2913F2F000CECEC0 /* ApplyToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834D865B2913F2F000CECEC0 /* ApplyToAllView.swift */; }; 834D865D2913F2F000CECEC0 /* ApplyToAllView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834D865B2913F2F000CECEC0 /* ApplyToAllView.swift */; }; @@ -896,7 +888,6 @@ 8358A6EA2925461C00EE197C /* DisclosureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8358A6E92925461C00EE197C /* DisclosureView.swift */; }; 8359E4B325F0E1DF0023FE18 /* QuickAccessWidgetAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8359E4B225F0E1DF0023FE18 /* QuickAccessWidgetAction.swift */; }; 8359E4BD25F0E20C0023FE18 /* WidgetStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8359E4BC25F0E20C0023FE18 /* WidgetStatus.swift */; }; - 835AD14924A0F438009BC85A /* DeviceContactsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 835AD14824A0F438009BC85A /* DeviceContactsManager.swift */; }; 835AD88B24AA46B700BB934C /* PhoneNumberViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 835AD88A24AA46B700BB934C /* PhoneNumberViewController.swift */; }; 835B1B90245AD23C00E41FBE /* Helper.m in Sources */ = {isa = PBXBuildFile; fileRef = 415226881A692ECC00EC7BB6 /* Helper.m */; }; 835E624B2085ED56007470A2 /* MEGAPauseTransferRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 835E624A2085ED56007470A2 /* MEGAPauseTransferRequestDelegate.m */; }; @@ -987,7 +978,6 @@ 837B9C0B24781B1600D409B4 /* NodeActionBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 837B9C0924781B1600D409B4 /* NodeActionBuilderTests.swift */; }; 837C4F88235DFFA600FFE177 /* VerifyEmail.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 837C4F87235DFFA600FFE177 /* VerifyEmail.storyboard */; }; 837C4F8A235DFFBA00FFE177 /* VerifyEmailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 837C4F89235DFFBA00FFE177 /* VerifyEmailViewController.swift */; }; - 837DC5592565605100CA289E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 415227411A692F8D00EC7BB6 /* Localizable.strings */; }; 837DC7A8256D196400CA289E /* GetLinkDatePickerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 837DC7A2256D196200CA289E /* GetLinkDatePickerTableViewCell.swift */; }; 837DC7A9256D196400CA289E /* GetLinkStringTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 837DC7A3256D196200CA289E /* GetLinkStringTableViewCell.swift */; }; 837DC7AA256D196400CA289E /* GetLinkSwitchOptionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 837DC7A4256D196300CA289E /* GetLinkSwitchOptionTableViewCell.swift */; }; @@ -1005,6 +995,7 @@ 83850A4126B2AACD00C62304 /* EnterMeetingLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83850A3E26B2AACC00C62304 /* EnterMeetingLinkViewModel.swift */; }; 83850A4326B2BA3000C62304 /* MEGAChatResultDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83850A4226B2BA3000C62304 /* MEGAChatResultDelegate.swift */; }; 83850A4526B2BC1100C62304 /* MEGAProviderDelegate+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83850A4426B2BC1000C62304 /* MEGAProviderDelegate+Additions.swift */; }; + 838838712A97769300148640 /* WaitingRoomEntity+Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 838838702A97769300148640 /* WaitingRoomEntity+Mapper.swift */; }; 838A7B5625D55EFA00767DB0 /* CallUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 838A7B5525D55EFA00767DB0 /* CallUseCase.swift */; }; 838A7B6925D5678C00767DB0 /* CallRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 838A7B6825D5678C00767DB0 /* CallRepository.swift */; }; 838A849D2631C987004804A4 /* MegaChatResultRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 838A849C2631C987004804A4 /* MegaChatResultRequestDelegate.swift */; }; @@ -1070,7 +1061,6 @@ 83C49BC2265262AD00A7764B /* CallParticipantCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83C49BC1265262AD00A7764B /* CallParticipantCell.xib */; }; 83C7776A2A45C9B300C64B90 /* ChatMessageScheduledMeetingChangeType+Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C777692A45C9B300C64B90 /* ChatMessageScheduledMeetingChangeType+Mapper.swift */; }; 83C7C84827EDCF2D00CFB5E6 /* SaveMediaUseCaseOCWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C7C84727EDCF2D00CFB5E6 /* SaveMediaUseCaseOCWrapper.swift */; }; - 83C7C85227EDE81F00CFB5E6 /* PhotosLibraryRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C7C85127EDE81F00CFB5E6 /* PhotosLibraryRepository.swift */; }; 83C8372827D788B600AACDE1 /* CancellableTransferRouterOCWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C8372727D788B600AACDE1 /* CancellableTransferRouterOCWrapper.swift */; }; 83C9CE772A2758D00009549B /* ScheduleMeetingEndRecurrenceOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C9CE762A2758D00009549B /* ScheduleMeetingEndRecurrenceOptionsView.swift */; }; 83C9CE7D2A278F750009549B /* RecurrenceOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C9CE7C2A278F750009549B /* RecurrenceOptionView.swift */; }; @@ -1092,7 +1082,6 @@ 83DBDDDC29D723B300CF433B /* ScheduleMeetingRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DBDDDB29D723B300CF433B /* ScheduleMeetingRouter.swift */; }; 83DBDDDE29D723D300CF433B /* ScheduleMeetingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DBDDDD29D723D300CF433B /* ScheduleMeetingViewModel.swift */; }; 83DBDDE129D730AE00CF433B /* DetailDisclosureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DBDDE029D730AE00CF433B /* DetailDisclosureView.swift */; }; - 83DBDDE329D730C800CF433B /* TextFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DBDDE229D730C800CF433B /* TextFieldView.swift */; }; 83DBDDE529D730DA00CF433B /* TextDescriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DBDDE429D730DA00CF433B /* TextDescriptionView.swift */; }; 83DBDDE729D730EC00CF433B /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DBDDE629D730EC00CF433B /* ErrorView.swift */; }; 83DBDDE929D7310500CF433B /* DatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83DBDDE829D7310500CF433B /* DatePickerView.swift */; }; @@ -1138,6 +1127,7 @@ 940CEE3D2A83DA5E007DD202 /* WarningViewRouting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940CEE3B2A83DA5E007DD202 /* WarningViewRouting.swift */; }; 940CEE422A84C4C8007DD202 /* MEGAStore+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F2ACCC29AD6E19004FC7A3 /* MEGAStore+Additions.swift */; }; 940DB9142924D844004E7349 /* AudioSessionUseCaseOCWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940DB9132924D844004E7349 /* AudioSessionUseCaseOCWrapper.swift */; }; + 940F41382A9FC40B00C2543F /* MEGAIntentDomain in Frameworks */ = {isa = PBXBuildFile; productRef = 940F41372A9FC40B00C2543F /* MEGAIntentDomain */; }; 940F4DED2A80F4C400FB940A /* ItemListViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C7A3802A796E0C0090AEFC /* ItemListViewController+Additions.swift */; }; 9411354F1F00FD1500D33428 /* Cloud.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 415226691A692ECC00EC7BB6 /* Cloud.storyboard */; }; 941135501F00FFAB00D33428 /* BrowserViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41AB8EC01A7103B300E40A39 /* BrowserViewController.m */; }; @@ -1148,7 +1138,6 @@ 9411355C1F01008400D33428 /* MEGAStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 4147C2D31B4EE9BA00A37044 /* MEGAStore.m */; }; 941135601F01011B00D33428 /* PhotoCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 41AC33A31A926B55005118AF /* PhotoCollectionViewCell.m */; }; 941135641F01071600D33428 /* NSString+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 41BADDAC1A8E1659008408B3 /* NSString+MNZCategory.m */; }; - 941135651F011C8400D33428 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 415227411A692F8D00EC7BB6 /* Localizable.strings */; }; 941135661F01541100D33428 /* NSFileManager+MNZCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 41DC6F711D54E7080081832C /* NSFileManager+MNZCategory.m */; }; 9418DB292A4EE5FB002B0077 /* MEGAPermissions in Frameworks */ = {isa = PBXBuildFile; productRef = 9418DB282A4EE5FB002B0077 /* MEGAPermissions */; }; 9418DB2B2A4EE611002B0077 /* MEGAPermissionsMock in Frameworks */ = {isa = PBXBuildFile; productRef = 9418DB2A2A4EE611002B0077 /* MEGAPermissionsMock */; }; @@ -1166,6 +1155,7 @@ 94282D4028A3B29200F794BA /* MyAccountHallTableViewCell+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94282D3C28A3B29100F794BA /* MyAccountHallTableViewCell+Additions.swift */; }; 94282D4228A3B29200F794BA /* MyAccountHallViewController+TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94282D3D28A3B29200F794BA /* MyAccountHallViewController+TableViewDataSource.swift */; }; 94282D4828A3B3F000F794BA /* ContactsViewController+Backups.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94282D4728A3B3F000F794BA /* ContactsViewController+Backups.swift */; }; + 9428430D2AA7CFDD00278048 /* AudioSessionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9428430C2AA7CFDD00278048 /* AudioSessionRepository.swift */; }; 9428B9A02819499A00DB7250 /* ContextMenuManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9428B99F2819499A00DB7250 /* ContextMenuManager.swift */; }; 942A64F729C390410017F439 /* BrowserViewController+TargetAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942A64F629C390410017F439 /* BrowserViewController+TargetAction.swift */; }; 942A64F829C395ED0017F439 /* BrowserViewController+TargetAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942A64F629C390410017F439 /* BrowserViewController+TargetAction.swift */; }; @@ -1177,6 +1167,7 @@ 943092FA291440010092B99A /* NodeValidationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943092F9291440010092B99A /* NodeValidationRepository.swift */; }; 94312E5029352329005F0F1C /* AudioSessionUseCase+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94312E4F29352329005F0F1C /* AudioSessionUseCase+Additions.swift */; }; 943270612A66CEB60080D687 /* FolderLinkViewController+NodeActionViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943270602A66CEB60080D687 /* FolderLinkViewController+NodeActionViewControllerDelegate.swift */; }; + 94328FB82A9672B7005B94EB /* Search in Resources */ = {isa = PBXBuildFile; fileRef = 94114EDE2A8F3C3500B525B9 /* Search */; }; 9433A72328DB7D720041F0DB /* ContactLinkCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9433A72228DB7D720041F0DB /* ContactLinkCollectionViewCell.swift */; }; 94361BB72A273ABD00B5AB02 /* GetLinkViewController+SnackBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94361BB62A273ABD00B5AB02 /* GetLinkViewController+SnackBar.swift */; }; 943649D82A4D81B0006CE8B8 /* AlertModel+DevicePermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943649D62A4D81B0006CE8B8 /* AlertModel+DevicePermissions.swift */; }; @@ -1199,7 +1190,6 @@ 9440D6C128BE6D390010D167 /* ViewModePreferenceEntity+Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9440D6C028BE6D390010D167 /* ViewModePreferenceEntity+Mapper.swift */; }; 9441533328E460E0005EF5F9 /* ReportIssueAlertTypeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9441533128E460DF005EF5F9 /* ReportIssueAlertTypeModel.swift */; }; 9441533428E460E0005EF5F9 /* ReportIssueAlertDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9441533228E460DF005EF5F9 /* ReportIssueAlertDataModel.swift */; }; - 94417EAF296D825B007C9230 /* RubbishBinRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94417EAE296D825B007C9230 /* RubbishBinRepositoryTests.swift */; }; 9442009D2982EA92005521C2 /* APIEnvironmentRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9442009C2982EA92005521C2 /* APIEnvironmentRepository.swift */; }; 9442009F2982EAA6005521C2 /* LogSettingRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9442009E2982EAA6005521C2 /* LogSettingRepository.swift */; }; 9444859C2A2CAF1E006DA2B8 /* GetLinkAccessInfoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9444859B2A2CAF1E006DA2B8 /* GetLinkAccessInfoTableViewCell.swift */; }; @@ -1214,10 +1204,10 @@ 9449938F28BD312D001AE14B /* NodeValidationRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9449938E28BD312D001AE14B /* NodeValidationRepositoryTests.swift */; }; 944AD0B128EF039700AB1E59 /* BackupsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944AD0B028EF039700AB1E59 /* BackupsRepository.swift */; }; 944AD0B328EF651F00AB1E59 /* BackupsOCWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944AD0B228EF651F00AB1E59 /* BackupsOCWrapper.swift */; }; - 944DB356295EE64900CE4D83 /* RubbishBinRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944DB355295EE64900CE4D83 /* RubbishBinRepository.swift */; }; - 9450A38F299E62620031BBEB /* UserAttributeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9450A38E299E62620031BBEB /* UserAttributeRepository.swift */; }; - 9450A391299E9ABD0031BBEB /* UserAttributeEntity+Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9450A390299E9ABD0031BBEB /* UserAttributeEntity+Mapper.swift */; }; 9451F90A28229A11000ACE3B /* OfflineViewController+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9451F90928229A11000ACE3B /* OfflineViewController+ContextMenu.swift */; }; + 945223002A98AB1700C9DFAF /* SearchDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945222FF2A98AB1700C9DFAF /* SearchDemoApp.swift */; }; + 945223022A98AB1700C9DFAF /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945223012A98AB1700C9DFAF /* ContentView.swift */; }; + 9452230E2A98AB6300C9DFAF /* Search in Frameworks */ = {isa = PBXBuildFile; productRef = 9452230D2A98AB6300C9DFAF /* Search */; }; 945339CD2A6014FA00363DAD /* FileLinkViewController+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945339CC2A6014FA00363DAD /* FileLinkViewController+ContextMenu.swift */; }; 94548FC6299D537C0050A9CC /* ChangeNameViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94548FC5299D537C0050A9CC /* ChangeNameViewController+Additions.swift */; }; 9455CF532927F17200E6FB8F /* SnackBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9455CF522927F17200E6FB8F /* SnackBarViewModel.swift */; }; @@ -1229,9 +1219,7 @@ 94614C752910554200503174 /* TransferNodeTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94614C742910554200503174 /* TransferNodeTableViewCell.xib */; }; 94624CFB1F50484200D52504 /* MEGAExportRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 94624CFA1F50484200D52504 /* MEGAExportRequestDelegate.m */; }; 94624CFC1F50516900D52504 /* MEGAExportRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 94624CFA1F50484200D52504 /* MEGAExportRequestDelegate.m */; }; - 9464C2F528C65CDA00F8B082 /* CameraPositionEntity+Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9464C2F428C65CDA00F8B082 /* CameraPositionEntity+Mapper.swift */; }; 946544862A4446D50038D8FC /* PermissionAlertRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946544852A4446D50038D8FC /* PermissionAlertRouterTests.swift */; }; - 94658DD729B89B7A0046605A /* NodeActionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8BA146B29927784004389FE /* NodeActionRepository.swift */; }; 9467F576299F9E6600A24D4A /* UserAttributeEntityMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9467F575299F9E6600A24D4A /* UserAttributeEntityMappingTests.swift */; }; 946ACCC3299C0ADD00E6C0F5 /* MEGAPresentation in Frameworks */ = {isa = PBXBuildFile; productRef = 946ACCC2299C0ADD00E6C0F5 /* MEGAPresentation */; }; 946ACCC5299C0B0000E6C0F5 /* MEGAPresentation in Frameworks */ = {isa = PBXBuildFile; productRef = 946ACCC4299C0B0000E6C0F5 /* MEGAPresentation */; }; @@ -1239,7 +1227,12 @@ 946ACCC9299C0B5700E6C0F5 /* MEGAPresentation in Frameworks */ = {isa = PBXBuildFile; productRef = 946ACCC8299C0B5700E6C0F5 /* MEGAPresentation */; }; 946E783529C0F79800BB3FE6 /* SharedItemsSearchOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946E783429C0F79800BB3FE6 /* SharedItemsSearchOperation.swift */; }; 9470003929B77BCF00827E04 /* ActionWarningViewRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9470003829B77BCF00827E04 /* ActionWarningViewRouter.swift */; }; + 94712EF02A9C8E4800B5F2C9 /* HomeSearchResultsProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94712EEF2A9C8E4800B5F2C9 /* HomeSearchResultsProviding.swift */; }; + 94712EF42A9C8F9400B5F2C9 /* HomeSearchResultsProvidingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94712EF32A9C8F9400B5F2C9 /* HomeSearchResultsProvidingTests.swift */; }; + 94712EFA2A9C9F5700B5F2C9 /* MockNodeDetailsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94712EF92A9C9F5700B5F2C9 /* MockNodeDetailsUseCase.swift */; }; + 94712EFD2A9C9FAD00B5F2C9 /* MockSearchFileUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94712EFC2A9C9FAD00B5F2C9 /* MockSearchFileUseCase.swift */; }; 9472202C2A29E16600890CFB /* MyAccountHallViewController+TableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9472202B2A29E16600890CFB /* MyAccountHallViewController+TableViewDelegate.swift */; }; + 947381BA2A9626D000C4E8EC /* SearchResultsBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947381B92A9626D000C4E8EC /* SearchResultsBridge.swift */; }; 947755BB2A4C714800DAAEE5 /* ToolbarButtonsDisabler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947755BA2A4C714800DAAEE5 /* ToolbarButtonsDisabler.swift */; }; 947755BE2A4C74ED00DAAEE5 /* ToolbarButtonsDisablerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947755BD2A4C74ED00DAAEE5 /* ToolbarButtonsDisablerTests.swift */; }; 9479F02528BF93CA008578BE /* DNDTurnOnOptionEntity+Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9479F02428BF93C9008578BE /* DNDTurnOnOptionEntity+Mapper.swift */; }; @@ -1252,6 +1245,9 @@ 947C6CBA2A5D417200CF929E /* InviteYourFriendsViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947C6CB92A5D417200CF929E /* InviteYourFriendsViewController+Additions.swift */; }; 947C6CC62A5D92DE00CF929E /* UIViewController+BackBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947C6CC52A5D92DE00CF929E /* UIViewController+BackBarButtonItem.swift */; }; 947C6CC82A5D92DE00CF929E /* UIViewController+BackBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947C6CC52A5D92DE00CF929E /* UIViewController+BackBarButtonItem.swift */; }; + 948176C52A93E9DC00E3A181 /* QuickAccessWidgetManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948176C42A93E9DC00E3A181 /* QuickAccessWidgetManagerTests.swift */; }; + 948425DB2A94F34F0024FF1F /* MainTabBarController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948425D72A94EF020024FF1F /* MainTabBarController+Additions.swift */; }; + 948425E32A9504400024FF1F /* Search in Frameworks */ = {isa = PBXBuildFile; productRef = 948425E22A9504400024FF1F /* Search */; }; 9487BF3D29B6744000645D32 /* NodeActionRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9487BF3A29B66F2E00645D32 /* NodeActionRepositoryTests.swift */; }; 94892C921EFBEA3D00AEAC25 /* MEGASdkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4152268A1A692ECC00EC7BB6 /* MEGASdkManager.m */; }; 94892C931EFBEA4100AEAC25 /* MEGALogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 41A4204E1C4EA02C002E192E /* MEGALogger.m */; }; @@ -1264,9 +1260,6 @@ 9495DBBD2A40C08300FA22EE /* UIApplication+PermissionModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9495DBBC2A40C08300FA22EE /* UIApplication+PermissionModal.swift */; }; 949CEACF1F05121D00A3A580 /* OpenAppRequiredViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 949CEACE1F05121D00A3A580 /* OpenAppRequiredViewController.m */; }; 94A1A26F2A83B11F00669C41 /* UserImageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF90C794260406FC006061F8 /* UserImageUseCase.swift */; }; - 94A1A2712A83B14E00669C41 /* UserImageLoadErrorEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF782D44269BA4BA0001D169 /* UserImageLoadErrorEntity.swift */; }; - 94A1A2732A83B16000669C41 /* UserImageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF90C7632603FB42006061F8 /* UserImageRepositoryProtocol.swift */; }; - 94A1A2752A83B1B900669C41 /* UserImageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF90C77F2603FD4A006061F8 /* UserImageRepository.swift */; }; 94A1A2782A83B29000669C41 /* UserStoreRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF90C7A626040930006061F8 /* UserStoreRepository.swift */; }; 94A220182A696502004130C4 /* MEGAAnalyticsiOS in Frameworks */ = {isa = PBXBuildFile; productRef = 94A220172A696502004130C4 /* MEGAAnalyticsiOS */; }; 94A518EF2A6ED0E700728730 /* ChatViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A518EE2A6ED0E700728730 /* ChatViewControllerTests.swift */; }; @@ -1290,6 +1283,7 @@ 94E7180B283D6603000EDDDF /* ChatRoomsViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E7180A283D6603000EDDDF /* ChatRoomsViewController+Additions.swift */; }; 94E8304A29DAD18100869522 /* OnboardingViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94E8304929DAD18100869522 /* OnboardingViewController+Additions.swift */; }; 94F2DD8029EFED3200718176 /* AchievementsDetailsViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F2DD7F29EFED3200718176 /* AchievementsDetailsViewController+Additions.swift */; }; + 94FD8A8B2AA0C6F300CD1C8B /* MEGARepo in Frameworks */ = {isa = PBXBuildFile; productRef = 94FD8A8A2AA0C6F300CD1C8B /* MEGARepo */; }; 990AC4ED2A4A77EC008E298F /* ChatRoomLinkViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990AC4EC2A4A77EC008E298F /* ChatRoomLinkViewModelTests.swift */; }; 990AC4F02A4ABEB7008E298F /* ChatRoomLinkViewModel+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990AC4EF2A4ABEB7008E298F /* ChatRoomLinkViewModel+Additions.swift */; }; 9911BA8B2A8B20C300B88F28 /* WaitingRoomViewRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9911BA8A2A8B20C300B88F28 /* WaitingRoomViewRouter.swift */; }; @@ -1357,7 +1351,6 @@ A802B1FA1FAB293700AC8BB0 /* MEGAChatBaseRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A802B1F91FAB293700AC8BB0 /* MEGAChatBaseRequestDelegate.m */; }; A802B1FD1FAB295A00AC8BB0 /* MEGAChatStartCallRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A802B1FC1FAB295A00AC8BB0 /* MEGAChatStartCallRequestDelegate.m */; }; A802B2011FAB2EA500AC8BB0 /* MEGAChatAnswerCallRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A802B2001FAB2EA500AC8BB0 /* MEGAChatAnswerCallRequestDelegate.m */; }; - A803C8FB2955DB550056C9F0 /* MEGADomain in Frameworks */ = {isa = PBXBuildFile; productRef = A803C8FA2955DB550056C9F0 /* MEGADomain */; }; A803C8FD2955DB790056C9F0 /* SAMKeychain in Frameworks */ = {isa = PBXBuildFile; productRef = A803C8FC2955DB790056C9F0 /* SAMKeychain */; }; A808F1832795D286004E4D31 /* ReportIssueViewRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A808F1822795D286004E4D31 /* ReportIssueViewRouter.swift */; }; A811CE2C22EAFB1700B24E76 /* SearchOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = A811CE2B22EAFB1700B24E76 /* SearchOperation.m */; }; @@ -1383,7 +1376,6 @@ A81F2C6A1F334FB2008600A5 /* MEGAProcessAsset.m in Sources */ = {isa = PBXBuildFile; fileRef = A81AD7381F3099F800CA4059 /* MEGAProcessAsset.m */; }; A81F9BB427EA023A00D47C0D /* CookieSettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A81F9BB327EA023A00D47C0D /* CookieSettingsViewModelTests.swift */; }; A820DA3C1F0E5BDC00F1F832 /* MEGAGetThumbnailRequestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A820DA3B1F0E5BDC00F1F832 /* MEGAGetThumbnailRequestDelegate.m */; }; - A821A5902862257400DB7017 /* IntentPersonProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A821A58F2862257400DB7017 /* IntentPersonProvider.swift */; }; A8278D7F200E72A00040B076 /* incoming_voice_video_call_iOS9.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = A8278D7E200E72A00040B076 /* incoming_voice_video_call_iOS9.mp3 */; }; A828E681286B3112004CED9E /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = A828E680286B3112004CED9E /* FirebaseCrashlytics */; }; A828E683286B3150004CED9E /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = A828E682286B3150004CED9E /* FirebaseCrashlytics */; }; @@ -1411,8 +1403,6 @@ A84F4A96298904860017ACB4 /* MEGAChatSdk+SharedInstanceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A84F4A95298904860017ACB4 /* MEGAChatSdk+SharedInstanceWrapper.swift */; }; A84F4A99298904860017ACB4 /* MEGAChatSdk+SharedInstanceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A84F4A95298904860017ACB4 /* MEGAChatSdk+SharedInstanceWrapper.swift */; }; A84F4A9A298904860017ACB4 /* MEGAChatSdk+SharedInstanceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A84F4A95298904860017ACB4 /* MEGAChatSdk+SharedInstanceWrapper.swift */; }; - A854C21D29AE1FB800CB37E7 /* NodeAttributeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8919421299F8E6300B2A225 /* NodeAttributeRepository.swift */; }; - A854C21E29AE27AD00CB37E7 /* NodeAttributeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8919421299F8E6300B2A225 /* NodeAttributeRepository.swift */; }; A855D23E2A796634001D2168 /* ChatRepo in Frameworks */ = {isa = PBXBuildFile; productRef = A855D23D2A796634001D2168 /* ChatRepo */; }; A85740C42032F6B80033034C /* MOChatDraft+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = A85740C02032F6B70033034C /* MOChatDraft+CoreDataProperties.m */; }; A85740C52032F6B80033034C /* MOChatDraft+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = A85740C22032F6B80033034C /* MOChatDraft+CoreDataClass.m */; }; @@ -1448,7 +1438,6 @@ A88E7FEC2549FD8A001B0A66 /* RecentsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A88E7FE72549FD8A001B0A66 /* RecentsViewController.m */; }; A88E7FED2549FD8A001B0A66 /* Recents.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A88E7FE82549FD8A001B0A66 /* Recents.storyboard */; }; A88E7FEE2549FD8A001B0A66 /* RecentsTableViewHeaderFooterView.m in Sources */ = {isa = PBXBuildFile; fileRef = A88E7FE92549FD8A001B0A66 /* RecentsTableViewHeaderFooterView.m */; }; - A8919424299F90A700B2A225 /* NodeAttributeRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8919423299F90A700B2A225 /* NodeAttributeRepositoryTests.swift */; }; A891E8A529C1E09D0020072A /* MEGASdk in Frameworks */ = {isa = PBXBuildFile; productRef = A891E8A429C1E09D0020072A /* MEGASdk */; }; A891E8A729C1E0A70020072A /* MEGASdk in Frameworks */ = {isa = PBXBuildFile; productRef = A891E8A629C1E0A70020072A /* MEGASdk */; }; A891E8A929C1E0B00020072A /* MEGASdk in Frameworks */ = {isa = PBXBuildFile; productRef = A891E8A829C1E0B00020072A /* MEGASdk */; }; @@ -1460,7 +1449,6 @@ A899467B297189F5008448C4 /* MEGAConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = B60DDC1521A7A7930097E4A3 /* MEGAConstants.m */; }; A899F0E8278F3567001A2D43 /* CompressingLogFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A899F0E7278F3567001A2D43 /* CompressingLogFileManager.m */; }; A89D72712795C5F500284BFC /* ReportIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89D72702795C5F500284BFC /* ReportIssueView.swift */; }; - A8A3D3742980383300037036 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 415227411A692F8D00EC7BB6 /* Localizable.strings */; }; A8A715C226307B0A00DDC8E2 /* UIPasteboard+WebP.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A715C126307B0A00DDC8E2 /* UIPasteboard+WebP.swift */; }; A8A716D62639A6B400DDC8E2 /* TurnOnNotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A716D52639A6B400DDC8E2 /* TurnOnNotificationsViewController.swift */; }; A8A716E02639A71800DDC8E2 /* TurnOnNotificationsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A716DF2639A71800DDC8E2 /* TurnOnNotificationsViewModel.swift */; }; @@ -1481,7 +1469,6 @@ A8C1B73E27734A22007095F4 /* OfflineTableViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C1B73D27734A22007095F4 /* OfflineTableViewController+Additions.swift */; }; A8C1B7B3277384BC007095F4 /* OfflineCollectionViewController+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C1B7B2277384BC007095F4 /* OfflineCollectionViewController+ContextMenu.swift */; }; A8C1B7BD27748A84007095F4 /* SharedItemsViewController+ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C1B7BC27748A84007095F4 /* SharedItemsViewController+ContextMenu.swift */; }; - A8C4754E299A78DD0074511F /* NodeActionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8BA146B29927784004389FE /* NodeActionRepository.swift */; }; A8C47595299D03EE0074511F /* MEGAConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = B60DDC1521A7A7930097E4A3 /* MEGAConstants.m */; }; A8C6494B2A39B8B0009F59ED /* CameraUploadOperation+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C6494A2A39B8B0009F59ED /* CameraUploadOperation+Additions.swift */; }; A8C93C0C22D718030030CE1B /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C93C0B22D718030030CE1B /* ProfileViewController.swift */; }; @@ -1545,6 +1532,7 @@ B52B8C68297FE9A400981F47 /* RecentsViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52B8C67297FE9A400981F47 /* RecentsViewController+Additions.swift */; }; B52CB2342A7FA78A00F9C56C /* OfflineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52CB2332A7FA78A00F9C56C /* OfflineViewModel.swift */; }; B52E231D28A0D66F0018C3C3 /* FeatureFlagUseCase+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52E231C28A0D66F0018C3C3 /* FeatureFlagUseCase+Additions.swift */; }; + B53B02BE2A96135200550A69 /* MEGAPurchase+PromotedPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B02BD2A96135200550A69 /* MEGAPurchase+PromotedPlan.swift */; }; B541CD302A6AAF53004B4CCC /* PlanSelectionSnackBarType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B541CD2F2A6AAF53004B4CCC /* PlanSelectionSnackBarType.swift */; }; B547025228B8AFB6005F7F71 /* MEGAPhotoBrowserViewController+LiveText.swift in Sources */ = {isa = PBXBuildFile; fileRef = B547025128B8AFB6005F7F71 /* MEGAPhotoBrowserViewController+LiveText.swift */; }; B547C0E629FB716C0008BEF2 /* SortOrderType+Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B547C0E529FB716C0008BEF2 /* SortOrderType+Mapper.swift */; }; @@ -1574,7 +1562,6 @@ B58050C12A823A6100D377D1 /* MockABTestProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58050C02A823A6100D377D1 /* MockABTestProvider.swift */; }; B582965A27C5DF50004DD7A5 /* TransferInventoryUseCaseHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B582965927C5DF50004DD7A5 /* TransferInventoryUseCaseHelper.swift */; }; B582966A27C5EADF004DD7A5 /* CreateAccountViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B582966927C5EADF004DD7A5 /* CreateAccountViewController+Additions.swift */; }; - B58E375129E808A000188287 /* ContactsPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 834AFD202498E7E1005DF37C /* ContactsPickerViewController.swift */; }; B58F7BFD298A8ABF0046F0FA /* ShareViewController+SecureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58F7BFB298A88750046F0FA /* ShareViewController+SecureFlag.swift */; }; B5902A812872F38200B450E9 /* MEGANodeList+SortOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5902A802872F38100B450E9 /* MEGANodeList+SortOrder.swift */; }; B5925B79279115FA00170CD6 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5925B78279115FA00170CD6 /* Colors.xcassets */; }; @@ -1591,11 +1578,6 @@ B5967A0829403AE500AD4E61 /* VerifyCredentialsViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5967A0729403AE500AD4E61 /* VerifyCredentialsViewController+Additions.swift */; }; B596D12B2977DC34002C5094 /* NodeInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596D1292977DC33002C5094 /* NodeInfoViewModel.swift */; }; B596D15C2979280A002C5094 /* CloudDriveViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596D15B2979280A002C5094 /* CloudDriveViewModel.swift */; }; - B5A99638282CF625008B8ADD /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = B5A99634282CF625008B8ADD /* Localizable.stringsdict */; }; - B5A9963A282CF625008B8ADD /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = B5A99634282CF625008B8ADD /* Localizable.stringsdict */; }; - B5A9963B282CF625008B8ADD /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = B5A99634282CF625008B8ADD /* Localizable.stringsdict */; }; - B5A9963C282CF625008B8ADD /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = B5A99634282CF625008B8ADD /* Localizable.stringsdict */; }; - B5A9963D282CF625008B8ADD /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = B5A99634282CF625008B8ADD /* Localizable.stringsdict */; }; B5AD33C529D136A600707496 /* UpgradeAccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AD33C429D136A500707496 /* UpgradeAccountViewModel.swift */; }; B5B0E30627F1BCA700DB6146 /* WarningViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B0E30527F1BCA700DB6146 /* WarningViewModelTests.swift */; }; B5BEB0FE2907C5F5006A442D /* CameraUploadsTableViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BEB0FD2907C5F5006A442D /* CameraUploadsTableViewController+Additions.swift */; }; @@ -1618,7 +1600,6 @@ B5E9D3FD28ACEB0800504F1F /* FeatureFlagViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E9D3FB28ACEB0800504F1F /* FeatureFlagViewModel.swift */; }; B5E9D41528ACF8CE00504F1F /* FeatureFlagViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E9D41428ACF8CE00504F1F /* FeatureFlagViewModelTests.swift */; }; B5EF0A7729F0EE2100B166B1 /* AccountPlanPurchaseRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EF0A7529F0EE2000B166B1 /* AccountPlanPurchaseRepository.swift */; }; - B5EF0A8F29F145C600B166B1 /* AccountPlanEntity+Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EF0A8329F11E7400B166B1 /* AccountPlanEntity+Mapper.swift */; }; B5FC636229E8F53400EE4C8E /* NodeCollectionViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FC636129E8F53400EE4C8E /* NodeCollectionViewCellViewModel.swift */; }; B5FC636429E9283C00EE4C8E /* NodeCollectionViewCell+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FC636329E9283C00EE4C8E /* NodeCollectionViewCell+Additions.swift */; }; B5FC636929E9351100EE4C8E /* NodeCollectionViewCellViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FC636829E9351100EE4C8E /* NodeCollectionViewCellViewModelTests.swift */; }; @@ -1980,6 +1961,7 @@ BF2675DB2A32A3F300736759 /* WeekDaysInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2675DA2A32A3F300736759 /* WeekDaysInformation.swift */; }; BF2675DD2A3307BC00736759 /* ScheduleMeetingCreationFrequencyOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2675DC2A3307BC00736759 /* ScheduleMeetingCreationFrequencyOption.swift */; }; BF2675E92A38626400736759 /* ScheduleMeetingSelectedFrequencyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2675E82A38626400736759 /* ScheduleMeetingSelectedFrequencyDetails.swift */; }; + BF29179D2A9D8DD800A59D12 /* ChatRepo in Frameworks */ = {isa = PBXBuildFile; productRef = BF29179C2A9D8DD800A59D12 /* ChatRepo */; }; BF2A7FFA29DE454300AAC203 /* CallInProgressTimeReporting.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2A7FF929DE454300AAC203 /* CallInProgressTimeReporting.swift */; }; BF2B2C82263F549D006E12A4 /* EndMeetingOptionsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B2C81263F549D006E12A4 /* EndMeetingOptionsViewModelTests.swift */; }; BF30471F2892048700A6988F /* MeetingFloatingPanelViewModel+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF30471E2892048700A6988F /* MeetingFloatingPanelViewModel+Additions.swift */; }; @@ -2009,7 +1991,6 @@ BF53B78C2745F92F00B47CA0 /* MEGAChatCall+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF53B78B2745F92F00B47CA0 /* MEGAChatCall+Additions.swift */; }; BF53B78D2745F92F00B47CA0 /* MEGAChatCall+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF53B78B2745F92F00B47CA0 /* MEGAChatCall+Additions.swift */; }; BF5455E92844377600544A53 /* EndCallDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5455E82844377600544A53 /* EndCallDialog.swift */; }; - BF5BC7742611687A00716CD6 /* UserInviteRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5BC7732611687A00716CD6 /* UserInviteRepository.swift */; }; BF5C36792491C917004DDA2F /* MEGAUser+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB463F624342F7F00FB90B9 /* MEGAUser+Additions.swift */; }; BF5E336C2558BE2A0055395E /* ExplorerBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E336B2558BE2A0055395E /* ExplorerBaseViewController.swift */; }; BF5E33762558BE620055395E /* ExplorerToolbarConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E33752558BE620055395E /* ExplorerToolbarConfigurator.swift */; }; @@ -2023,9 +2004,22 @@ BF5E340E2559E2EC0055395E /* FilesExplorerContainerGridViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E340D2559E2EC0055395E /* FilesExplorerContainerGridViewState.swift */; }; BF5E345F255A2E1A0055395E /* FilesExplorerGridSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E345E255A2E1A0055395E /* FilesExplorerGridSource.swift */; }; BF5E3495255C8FD10055395E /* FilesExplorerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E3494255C8FD10055395E /* FilesExplorerViewController.swift */; }; + BF6149522A9FF33300AB051A /* MEGAL10n in Frameworks */ = {isa = PBXBuildFile; productRef = BF6149512A9FF33300AB051A /* MEGAL10n */; }; + BF6149542A9FF33300AB051A /* MEGAL10nObjc in Frameworks */ = {isa = PBXBuildFile; productRef = BF6149532A9FF33300AB051A /* MEGAL10nObjc */; }; + BF6149562A9FF34400AB051A /* MEGAL10n in Frameworks */ = {isa = PBXBuildFile; productRef = BF6149552A9FF34400AB051A /* MEGAL10n */; }; + BF6149582A9FF34400AB051A /* MEGAL10nObjc in Frameworks */ = {isa = PBXBuildFile; productRef = BF6149572A9FF34400AB051A /* MEGAL10nObjc */; }; + BF61495A2A9FF35200AB051A /* MEGAL10n in Frameworks */ = {isa = PBXBuildFile; productRef = BF6149592A9FF35200AB051A /* MEGAL10n */; }; + BF61495C2A9FF35200AB051A /* MEGAL10nObjc in Frameworks */ = {isa = PBXBuildFile; productRef = BF61495B2A9FF35200AB051A /* MEGAL10nObjc */; }; + BF61495E2A9FF36200AB051A /* MEGAL10n in Frameworks */ = {isa = PBXBuildFile; productRef = BF61495D2A9FF36200AB051A /* MEGAL10n */; }; + BF6149602A9FF36200AB051A /* MEGAL10nObjc in Frameworks */ = {isa = PBXBuildFile; productRef = BF61495F2A9FF36200AB051A /* MEGAL10nObjc */; }; + BF6149622A9FF37000AB051A /* MEGAL10n in Frameworks */ = {isa = PBXBuildFile; productRef = BF6149612A9FF37000AB051A /* MEGAL10n */; }; + BF6149642A9FF37000AB051A /* MEGAL10nObjc in Frameworks */ = {isa = PBXBuildFile; productRef = BF6149632A9FF37000AB051A /* MEGAL10nObjc */; }; + BF6149662A9FF38400AB051A /* MEGAL10n in Frameworks */ = {isa = PBXBuildFile; productRef = BF6149652A9FF38400AB051A /* MEGAL10n */; }; + BF6149682A9FF38400AB051A /* MEGAL10nObjc in Frameworks */ = {isa = PBXBuildFile; productRef = BF6149672A9FF38400AB051A /* MEGAL10nObjc */; }; + BF61496A2A9FF39500AB051A /* MEGAL10n in Frameworks */ = {isa = PBXBuildFile; productRef = BF6149692A9FF39500AB051A /* MEGAL10n */; }; + BF61496C2A9FF39500AB051A /* MEGAL10nObjc in Frameworks */ = {isa = PBXBuildFile; productRef = BF61496B2A9FF39500AB051A /* MEGAL10nObjc */; }; BF66CA7728B83ACF00DB686E /* HangOrEndCallViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66CA7628B83ACF00DB686E /* HangOrEndCallViewModelTests.swift */; }; BF66DF162612C20200066F35 /* ChatRoomRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66DF152612C20200066F35 /* ChatRoomRepository.swift */; }; - BF66DF362612CE3100066F35 /* InviteRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66DF352612CE3100066F35 /* InviteRequestDelegate.swift */; }; BF66DFBE2613CC4500066F35 /* ChatRoomEntity+Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66DFBD2613CC4500066F35 /* ChatRoomEntity+Mapper.swift */; }; BF66DFC82613E6AC00066F35 /* MeetingFloatingPanelRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66DFC72613E6AC00066F35 /* MeetingFloatingPanelRouter.swift */; }; BF66DFD22613E6DC00066F35 /* MeetingFloatingPanelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66DFD12613E6DC00066F35 /* MeetingFloatingPanelViewModel.swift */; }; @@ -2050,10 +2044,8 @@ BF6DF36B2457A06300D612D0 /* AddToChatImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6DF3692457A06300D612D0 /* AddToChatImageCell.swift */; }; BF6DF36C2457A06300D612D0 /* AddToChatImageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF6DF36A2457A06300D612D0 /* AddToChatImageCell.xib */; }; BF6E8D2D25B636FB003A125A /* PSAViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6E8D2C25B636FB003A125A /* PSAViewModelTests.swift */; }; - BF6E8DFF25BA556C003A125A /* MainTabBarController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6E8DFE25BA556B003A125A /* MainTabBarController+Additions.swift */; }; BF6E8F6D25C0C76A003A125A /* MEGANode+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6790E05256B81C600154F6D /* MEGANode+Additions.swift */; }; BF74E1E3253668E1001F663C /* HomeRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF74E1E2253668E1001F663C /* HomeRouter.swift */; }; - BF782D6D269BA4BB0001D169 /* UserImageLoadErrorEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF782D44269BA4BA0001D169 /* UserImageLoadErrorEntity.swift */; }; BF7D43B4253CFF6F003DF1DA /* SDKNodesUpdateListenerRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7D43B3253CFF6F003DF1DA /* SDKNodesUpdateListenerRepository.swift */; }; BF7D43BB253D0930003DF1DA /* Sequence+MEGANode.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7D43BA253D0930003DF1DA /* Sequence+MEGANode.swift */; }; BF7D43D8253D31D7003DF1DA /* SDKTransferListenerRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7D43D7253D31D7003DF1DA /* SDKTransferListenerRepository.swift */; }; @@ -2082,8 +2074,6 @@ BF89C8A42947259B00710FD1 /* ChatRoomViewModel+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF89C8A32947259B00710FD1 /* ChatRoomViewModel+Additions.swift */; }; BF8B02EE28359EDD009AC794 /* callEnded.wav in Resources */ = {isa = PBXBuildFile; fileRef = BF8B02EB28359EDD009AC794 /* callEnded.wav */; }; BF8B02F32835A09E009AC794 /* MEGAProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8B02F22835A09E009AC794 /* MEGAProviderDelegate.swift */; }; - BF90C7642603FB42006061F8 /* UserImageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF90C7632603FB42006061F8 /* UserImageRepositoryProtocol.swift */; }; - BF90C7802603FD4A006061F8 /* UserImageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF90C77F2603FD4A006061F8 /* UserImageRepository.swift */; }; BF90C795260406FC006061F8 /* UserImageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF90C794260406FC006061F8 /* UserImageUseCase.swift */; }; BF90C7A726040930006061F8 /* UserStoreRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF90C7A626040930006061F8 /* UserStoreRepository.swift */; }; BF923EC72A007DEB00C4D8B0 /* ScheduleMeetingCreationRecurrenceOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF923EC62A007DEB00C4D8B0 /* ScheduleMeetingCreationRecurrenceOptionsView.swift */; }; @@ -2150,8 +2140,6 @@ BFAB115123CEA5C300BCE72D /* AlbumTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAB113523CEA5C300BCE72D /* AlbumTableViewCell.swift */; }; BFAB115223CEA5C300BCE72D /* AlbumTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFAB113623CEA5C300BCE72D /* AlbumTableViewCell.xib */; }; BFAB115323CEA5C300BCE72D /* AlbumsTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAB113723CEA5C300BCE72D /* AlbumsTableViewDataSource.swift */; }; - BFAC873925FEF26C009CA3F6 /* AudioSessionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAC873825FEF26C009CA3F6 /* AudioSessionRepository.swift */; }; - BFAC87972600041C009CA3F6 /* CaptureDeviceRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAC87962600041C009CA3F6 /* CaptureDeviceRepository.swift */; }; BFAD0F982A2978D2003F5F83 /* ScheduleMeetingCreationCustomOptionsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAD0F972A2978D2003F5F83 /* ScheduleMeetingCreationCustomOptionsRouter.swift */; }; BFAD0F9A2A297DEA003F5F83 /* ScheduleMeetingCreationCustomOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAD0F992A297DEA003F5F83 /* ScheduleMeetingCreationCustomOptionsView.swift */; }; BFAD0F9C2A297DF3003F5F83 /* ScheduleMeetingCreationCustomOptionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAD0F9B2A297DF3003F5F83 /* ScheduleMeetingCreationCustomOptionsViewModel.swift */; }; @@ -2248,7 +2236,6 @@ BFD6536025EDE8E500052DE8 /* MeetingParticipantInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD6535E25EDE8E500052DE8 /* MeetingParticipantInfoViewController.swift */; }; BFD6557525F59EC600052DE8 /* CircularView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD6557425F59EC600052DE8 /* CircularView.swift */; }; BFD6564F25F6D22300052DE8 /* MeetingQuickActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD6564E25F6D22300052DE8 /* MeetingQuickActionView.swift */; }; - BFDE0EEB263A44BC00FC586E /* MockUserInviteUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDE0EEA263A44BC00FC586E /* MockUserInviteUseCase.swift */; }; BFDEE87A297118D1004B585D /* NotificationsTableViewController+Meetings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDEE879297118D1004B585D /* NotificationsTableViewController+Meetings.swift */; }; BFE3B9D328569B6B003949FF /* MeetingNoUserJoinedRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE3B9D228569B6B003949FF /* MeetingNoUserJoinedRepository.swift */; }; BFE3B9D728569BAD003949FF /* MeetingNoUserJoinedRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE3B9D628569BAD003949FF /* MeetingNoUserJoinedRepositoryProtocol.swift */; }; @@ -2521,12 +2508,11 @@ 0DA5BCB725BA403800530F70 /* DefaultTabTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTabTableViewController.swift; sourceTree = ""; }; 0E10BD5F2A7231EE00AE2980 /* AlbumNameUseCase+AdditionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumNameUseCase+AdditionsTests.swift"; sourceTree = ""; }; 0E1904632A92FF410048354A /* EmptyAlbumView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyAlbumView.swift; sourceTree = ""; }; + 0E2363B42A9EDDCC0019BBBE /* AlbumEntity+Analytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlbumEntity+Analytics.swift"; sourceTree = ""; }; 0E2A4EE729D4FDFE00EB6ECE /* PhotoLibraryPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryPublisherTests.swift; sourceTree = ""; }; 0E2FD9DC2A258C3B00FB75F1 /* GetAlbumsLinkViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetAlbumsLinkViewModelTests.swift; sourceTree = ""; }; 0E2FEAA62A7B44AC00D237C6 /* GetLinkView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetLinkView.swift; sourceTree = ""; }; 0E2FEAA92A7B463600D237C6 /* GetLinkRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetLinkRouter.swift; sourceTree = ""; }; - 0E32FFA9293E8179004BB11D /* NodeUpdateRepositoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeUpdateRepositoryTests.swift; sourceTree = ""; }; - 0E32FFB1293E896A004BB11D /* NodeUpdateRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeUpdateRepository.swift; sourceTree = ""; }; 0E3B1A342A1D910B00EC5859 /* GetLinkAlbumInfoCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetLinkAlbumInfoCellViewModel.swift; sourceTree = ""; }; 0E3B1A382A1D919B00EC5859 /* GetLinkAlbumInfoCellViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetLinkAlbumInfoCellViewModelTests.swift; sourceTree = ""; }; 0E3B1A472A1DB9FD00EC5859 /* GetLinkSwitchOptionCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetLinkSwitchOptionCellViewModel.swift; sourceTree = ""; }; @@ -2556,6 +2542,7 @@ 0ECDCD4F2A1DDEEA00FBEFE7 /* GetLinkSwitchCellViewConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetLinkSwitchCellViewConfiguration.swift; sourceTree = ""; }; 0EDE0DDA2A20587E0006A9F1 /* GetAlbumsLinksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetAlbumsLinksViewModel.swift; sourceTree = ""; }; 0EDE0DDC2A2058DE0006A9F1 /* ShareAlbumsLinkInitialSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAlbumsLinkInitialSections.swift; sourceTree = ""; }; + 0EE990932AA55D9300D43714 /* ChatAttachedNodesViewController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatAttachedNodesViewController+Additions.swift"; sourceTree = ""; }; 12084047286120020079127B /* SearchOperation+Initialize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchOperation+Initialize.swift"; sourceTree = ""; }; 1208CEC427FA49EB00F514D4 /* PhotoCellVideoDurationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCellVideoDurationViewModifier.swift; sourceTree = ""; }; 122977DD27DA976F0048F381 /* MediaDiscoveryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDiscoveryViewModel.swift; sourceTree = ""; }; @@ -2647,6 +2634,8 @@ 246013112A459166007EF5ED /* AdvancedViewRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedViewRouterTests.swift; sourceTree = ""; }; 2461E7032A8CC90600A8FFA6 /* StreamingInfoRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamingInfoRepositoryTests.swift; sourceTree = ""; }; 246A311E2A45709200BF8DDB /* AdvancedTableViewController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdvancedTableViewController+Additions.swift"; sourceTree = ""; }; + 246ACEB42AA2D9C5000BD2A8 /* AudioPlayerViewRouterNodeActionAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerViewRouterNodeActionAdapterTests.swift; sourceTree = ""; }; + 246ACEB82AA2DAEE000BD2A8 /* AudioPlayerViewRouterNodeActionAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerViewRouterNodeActionAdapter.swift; sourceTree = ""; }; 247FC1BD2A4F1C3700CC8518 /* QASettingsRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QASettingsRouterTests.swift; sourceTree = ""; }; 2488EBC22A13B503005F910E /* NodeActionViewControllerGenericDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeActionViewControllerGenericDelegateTests.swift; sourceTree = ""; }; 248A031A2A79026800F183A2 /* MEGAAVViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MEGAAVViewControllerTests.swift; sourceTree = ""; }; @@ -2666,11 +2655,17 @@ 2546EE042A4149B300CBB82E /* AVPlayerManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerManagerTests.swift; sourceTree = ""; }; 2562E8C42A37E73E0031D286 /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = ""; }; 2562E8C62A3921CE0031D286 /* ProfileTableViewDiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTableViewDiffableDataSource.swift; sourceTree = ""; }; + 2592C2A12AA00D3100F902C8 /* MEGAPhotoBrowserViewController+SnackBarPresenting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MEGAPhotoBrowserViewController+SnackBarPresenting.swift"; sourceTree = ""; }; 259FA4022A3A949F00C038B2 /* ProfileViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModelTests.swift; sourceTree = ""; }; 259FA4052A3A9C9F00C038B2 /* ProfileSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSection.swift; sourceTree = ""; }; + 25D840682A9EE0E50026D288 /* PermissionAlertViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionAlertViewModifier.swift; sourceTree = ""; }; + 25D8406B2A9EE2860026D288 /* PermissionAlertModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionAlertModel.swift; sourceTree = ""; }; + 25D840702A9EE9330026D288 /* PermissionAlertModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionAlertModelTests.swift; sourceTree = ""; }; 25DB82D22A3C07D100207E72 /* ProfileTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTableViewDataSource.swift; sourceTree = ""; }; 25E2717C2A3FE96800DDB678 /* AVPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerManager.swift; sourceTree = ""; }; 25E2717E2A40099800DDB678 /* MEGAAVViewController+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MEGAAVViewController+Combine.swift"; sourceTree = ""; }; + 25ED56692A98699E0063864F /* MockTransferWidgetResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTransferWidgetResponder.swift; sourceTree = ""; }; + 25ED56802A9D867D0063864F /* DownloadFileRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadFileRepositoryTests.swift; sourceTree = ""; }; 271FC4202A27EA4400E35924 /* ElementStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementStore.swift; sourceTree = ""; }; 274514C12A2F5BDE00BFFB2F /* ElementStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementStoreTests.swift; sourceTree = ""; }; 275682F82A4015280038E13A /* RemovalConfirmationMessageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemovalConfirmationMessageGenerator.swift; sourceTree = ""; }; @@ -2832,11 +2827,6 @@ 32F03D57287D225100C518A4 /* PhotosViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotosViewModelTests.swift; sourceTree = ""; }; 32F9E681283490620002132D /* AlbumToolbarConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumToolbarConfigurator.swift; sourceTree = ""; }; 32F9E683283490A40002132D /* AlbumToolbarProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumToolbarProvider.swift; sourceTree = ""; }; - 410333D71B9993A000380FF3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - 410333E11B99948600380FF3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; - 410333EB1B9994F400380FF3 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; - 410333EF1B99951900380FF3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; - 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 = ""; }; 4115D6421E718B1F004BB0FF /* UIImage+MNZCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+MNZCategory.h"; sourceTree = ""; }; @@ -2867,8 +2857,6 @@ 415226881A692ECC00EC7BB6 /* Helper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = Helper.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 415226891A692ECC00EC7BB6 /* MEGASdkManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MEGASdkManager.h; sourceTree = ""; }; 4152268A1A692ECC00EC7BB6 /* MEGASdkManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MEGASdkManager.m; sourceTree = ""; }; - 415227401A692F8D00EC7BB6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; - 415227421A692F9B00EC7BB6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 4156C8081AD7E79A00F5E818 /* LTHKeychainUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LTHKeychainUtils.h; sourceTree = ""; }; 4156C8091AD7E79A00F5E818 /* LTHKeychainUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LTHKeychainUtils.m; sourceTree = ""; }; 4156C80A1AD7E79A00F5E818 /* LTHPasscodeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LTHPasscodeViewController.h; sourceTree = ""; }; @@ -3108,7 +3096,6 @@ 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 = ""; }; - 5BFA962F252E1119003653EB /* ManageChatHistoryRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageChatHistoryRepository.swift; sourceTree = ""; }; 5D01995624AD64C1006ED2A0 /* InterfaceStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceStyle.swift; sourceTree = ""; }; 5D038F062511D5AF00DAA2E6 /* HomeSearchResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeSearchResultViewController.swift; sourceTree = ""; }; 5D038F072511D5AF00DAA2E6 /* HomeSearchResultViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HomeSearchResultViewController.xib; sourceTree = ""; }; @@ -3170,7 +3157,6 @@ 5D568208252FEA9F00FE7F9C /* SortingOrder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortingOrder.swift; sourceTree = ""; }; 5D56820A252FEC9800FE7F9C /* Reader+GroupingSorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Reader+GroupingSorting.swift"; sourceTree = ""; }; 5D5DE4F72511F88900F2B4FD /* SearchFileUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFileUseCase.swift; sourceTree = ""; }; - 5D609287255BA0AE005FA3E4 /* NodeFavouriteActionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeFavouriteActionRepository.swift; sourceTree = ""; }; 5D60928F255BC2B6005FA3E4 /* SDKError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKError.swift; sourceTree = ""; }; 5D609297255D5D40005FA3E4 /* NodeLabelActionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeLabelActionRepository.swift; sourceTree = ""; }; 5D6092A5255D609F005FA3E4 /* NodeLabelActionUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeLabelActionUseCase.swift; sourceTree = ""; }; @@ -3201,13 +3187,7 @@ 5D7D53462551263C00D3582E /* TextFieldStyleFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldStyleFactory.swift; sourceTree = ""; }; 5D7D6B9D2491F79E00ADBDEE /* OverDiskQuotaService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverDiskQuotaService.swift; sourceTree = ""; }; 5D816FC52564C08E0067D22F /* SlideIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideIndicatorView.swift; sourceTree = ""; }; - 5D8303BB256262350082CA18 /* BannerRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerRepository.swift; sourceTree = ""; }; 5D8303D1256266A80082CA18 /* GeneralDomainError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralDomainError.swift; sourceTree = ""; }; - 5D8303E6256270800082CA18 /* BannerEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerEntity.swift; sourceTree = ""; }; - 5D8303F4256367D60082CA18 /* BannerErrorEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerErrorEntity.swift; sourceTree = ""; }; - 5D83041125638DC40082CA18 /* MEGABannerList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MEGABannerList.swift; sourceTree = ""; }; - 5D83041F25638DE70082CA18 /* MEGABanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MEGABanner.swift; sourceTree = ""; }; - 5D83042E256392370082CA18 /* UserBannerUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBannerUseCase.swift; sourceTree = ""; }; 5D876542252CAAE200E6CB91 /* TableDataSourceConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableDataSourceConfiguration.swift; sourceTree = ""; }; 5D8A76ED24FCD0C800C0610C /* MEGANotificationUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MEGANotificationUseCase.swift; sourceTree = ""; }; 5D8A76EE24FCD0C900C0610C /* MEGAAvatarGeneratingUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MEGAAvatarGeneratingUseCase.swift; sourceTree = ""; }; @@ -3255,7 +3235,6 @@ 5DEFE99324A1AE0E00DFF256 /* MarkedStringParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkedStringParser.swift; sourceTree = ""; }; 5DF016802499BD60005A121D /* MEGAPlan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MEGAPlan.swift; sourceTree = ""; }; 5DF016822499BD7B005A121D /* MEGAPlanService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MEGAPlanService.swift; sourceTree = ""; }; - 5DF0E3CF2563A8540052CC95 /* MEGAResultMappingRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MEGAResultMappingRequestDelegate.swift; sourceTree = ""; }; 5DF0E3D02563A8540052CC95 /* MEGAResultRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MEGAResultRequestDelegate.swift; sourceTree = ""; }; 5DF249DC24F37589003FED76 /* FileUploadingRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadingRouter.swift; sourceTree = ""; }; 5DF249DE24F381C8003FED76 /* DispatchFunction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchFunction.swift; sourceTree = ""; }; @@ -3368,7 +3347,6 @@ 77F95CEE20E3B14D00F42D9C /* ShareDestinationTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareDestinationTableViewController.m; 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 = ""; }; - 830C268025DFC78D00E9B56E /* FavouriteNodesRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavouriteNodesRepository.swift; sourceTree = ""; }; 830C28B125E68F8400E9B56E /* QuickAccessWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickAccessWidgetView.swift; sourceTree = ""; }; 830C28CC25E68FC200E9B56E /* DetailItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailItemView.swift; sourceTree = ""; }; 830C28DF25E6900200E9B56E /* GridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridView.swift; sourceTree = ""; }; @@ -3466,9 +3444,6 @@ 8348A8D72901902B00B66463 /* ActiveCallViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveCallViewModel.swift; sourceTree = ""; }; 8348A8D92901909400B66463 /* ChatRoomActiveCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomActiveCallView.swift; sourceTree = ""; }; 834AFD1C2492567A005DF37C /* MEGAChatPeerList+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MEGAChatPeerList+Additions.swift"; sourceTree = ""; }; - 834AFD202498E7E1005DF37C /* ContactsPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsPickerViewController.swift; sourceTree = ""; }; - 834AFD222498E925005DF37C /* ContactsPicker.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ContactsPicker.storyboard; sourceTree = ""; }; - 834AFD242498F637005DF37C /* DeviceContactTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceContactTableViewCell.swift; sourceTree = ""; }; 834D7D9F2926699500634B66 /* LeaveChatButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveChatButtonView.swift; sourceTree = ""; }; 834D865B2913F2F000CECEC0 /* ApplyToAllView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplyToAllView.swift; sourceTree = ""; }; 834D867E29197E8800CECEC0 /* ChatRoomAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomAvatarView.swift; sourceTree = ""; }; @@ -3493,7 +3468,6 @@ 8358A6E92925461C00EE197C /* DisclosureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisclosureView.swift; sourceTree = ""; }; 8359E4B225F0E1DF0023FE18 /* QuickAccessWidgetAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickAccessWidgetAction.swift; sourceTree = ""; }; 8359E4BC25F0E20C0023FE18 /* WidgetStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetStatus.swift; sourceTree = ""; }; - 835AD14824A0F438009BC85A /* DeviceContactsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceContactsManager.swift; sourceTree = ""; }; 835AD88A24AA46B700BB934C /* PhoneNumberViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberViewController.swift; sourceTree = ""; }; 835E62492085ED56007470A2 /* MEGAPauseTransferRequestDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MEGAPauseTransferRequestDelegate.h; sourceTree = ""; }; 835E624A2085ED56007470A2 /* MEGAPauseTransferRequestDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MEGAPauseTransferRequestDelegate.m; sourceTree = ""; }; @@ -3599,6 +3573,7 @@ 83850A3E26B2AACC00C62304 /* EnterMeetingLinkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterMeetingLinkViewModel.swift; sourceTree = ""; }; 83850A4226B2BA3000C62304 /* MEGAChatResultDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MEGAChatResultDelegate.swift; sourceTree = ""; }; 83850A4426B2BC1000C62304 /* MEGAProviderDelegate+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MEGAProviderDelegate+Additions.swift"; sourceTree = ""; }; + 838838702A97769300148640 /* WaitingRoomEntity+Mapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WaitingRoomEntity+Mapper.swift"; sourceTree = ""; }; 838A7B5525D55EFA00767DB0 /* CallUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallUseCase.swift; sourceTree = ""; }; 838A7B6825D5678C00767DB0 /* CallRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallRepository.swift; sourceTree = ""; }; 838A849C2631C987004804A4 /* MegaChatResultRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MegaChatResultRequestDelegate.swift; sourceTree = ""; }; @@ -3657,7 +3632,6 @@ 83C49BC1265262AD00A7764B /* CallParticipantCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CallParticipantCell.xib; sourceTree = ""; }; 83C777692A45C9B300C64B90 /* ChatMessageScheduledMeetingChangeType+Mapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatMessageScheduledMeetingChangeType+Mapper.swift"; sourceTree = ""; }; 83C7C84727EDCF2D00CFB5E6 /* SaveMediaUseCaseOCWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveMediaUseCaseOCWrapper.swift; sourceTree = ""; }; - 83C7C85127EDE81F00CFB5E6 /* PhotosLibraryRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotosLibraryRepository.swift; sourceTree = ""; }; 83C8372727D788B600AACDE1 /* CancellableTransferRouterOCWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancellableTransferRouterOCWrapper.swift; sourceTree = ""; }; 83C9CE762A2758D00009549B /* ScheduleMeetingEndRecurrenceOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMeetingEndRecurrenceOptionsView.swift; sourceTree = ""; }; 83C9CE7C2A278F750009549B /* RecurrenceOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecurrenceOptionView.swift; sourceTree = ""; }; @@ -3676,7 +3650,6 @@ 83DBDDDB29D723B300CF433B /* ScheduleMeetingRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMeetingRouter.swift; sourceTree = ""; }; 83DBDDDD29D723D300CF433B /* ScheduleMeetingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMeetingViewModel.swift; sourceTree = ""; }; 83DBDDE029D730AE00CF433B /* DetailDisclosureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailDisclosureView.swift; sourceTree = ""; }; - 83DBDDE229D730C800CF433B /* TextFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldView.swift; sourceTree = ""; }; 83DBDDE429D730DA00CF433B /* TextDescriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextDescriptionView.swift; sourceTree = ""; }; 83DBDDE629D730EC00CF433B /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; 83DBDDE829D7310500CF433B /* DatePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerView.swift; sourceTree = ""; }; @@ -3716,6 +3689,7 @@ 9405E70D28A3AC43005A16D1 /* BackupNodesValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupNodesValidator.swift; sourceTree = ""; }; 940CEE3B2A83DA5E007DD202 /* WarningViewRouting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningViewRouting.swift; sourceTree = ""; }; 940DB9132924D844004E7349 /* AudioSessionUseCaseOCWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSessionUseCaseOCWrapper.swift; sourceTree = ""; }; + 940F41362A9ED39200C2543F /* MEGAIntentDomain */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MEGAIntentDomain; sourceTree = ""; }; 94114EDE2A8F3C3500B525B9 /* Search */ = {isa = PBXFileReference; lastKnownFileType = text; name = Search; path = Modules/Features/Search; sourceTree = SOURCE_ROOT; }; 9418DB272A4EE5E7002B0077 /* MEGAPermissions */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MEGAPermissions; sourceTree = ""; }; 9419B6C51F20DC5700FEBE31 /* CoreSpotlight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreSpotlight.framework; path = System/Library/Frameworks/CoreSpotlight.framework; sourceTree = SDKROOT; }; @@ -3732,6 +3706,7 @@ 94282D3C28A3B29100F794BA /* MyAccountHallTableViewCell+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MyAccountHallTableViewCell+Additions.swift"; sourceTree = ""; }; 94282D3D28A3B29200F794BA /* MyAccountHallViewController+TableViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MyAccountHallViewController+TableViewDataSource.swift"; sourceTree = ""; }; 94282D4728A3B3F000F794BA /* ContactsViewController+Backups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactsViewController+Backups.swift"; sourceTree = ""; }; + 9428430C2AA7CFDD00278048 /* AudioSessionRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioSessionRepository.swift; sourceTree = ""; }; 9428B99F2819499A00DB7250 /* ContextMenuManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuManager.swift; sourceTree = ""; }; 942A64F629C390410017F439 /* BrowserViewController+TargetAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BrowserViewController+TargetAction.swift"; sourceTree = ""; }; 942F4FD61F500A8F001FC4AC /* MEGASdk+MNZCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MEGASdk+MNZCategory.h"; sourceTree = ""; }; @@ -3778,7 +3753,6 @@ 9440D6C028BE6D390010D167 /* ViewModePreferenceEntity+Mapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewModePreferenceEntity+Mapper.swift"; sourceTree = ""; }; 9441533128E460DF005EF5F9 /* ReportIssueAlertTypeModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportIssueAlertTypeModel.swift; sourceTree = ""; }; 9441533228E460DF005EF5F9 /* ReportIssueAlertDataModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportIssueAlertDataModel.swift; sourceTree = ""; }; - 94417EAE296D825B007C9230 /* RubbishBinRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RubbishBinRepositoryTests.swift; sourceTree = ""; }; 9442009C2982EA92005521C2 /* APIEnvironmentRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIEnvironmentRepository.swift; sourceTree = ""; }; 9442009E2982EAA6005521C2 /* LogSettingRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogSettingRepository.swift; sourceTree = ""; }; 9444859B2A2CAF1E006DA2B8 /* GetLinkAccessInfoTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetLinkAccessInfoTableViewCell.swift; sourceTree = ""; }; @@ -3793,10 +3767,10 @@ 9449938E28BD312D001AE14B /* NodeValidationRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeValidationRepositoryTests.swift; sourceTree = ""; }; 944AD0B028EF039700AB1E59 /* BackupsRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupsRepository.swift; sourceTree = ""; }; 944AD0B228EF651F00AB1E59 /* BackupsOCWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupsOCWrapper.swift; sourceTree = ""; }; - 944DB355295EE64900CE4D83 /* RubbishBinRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RubbishBinRepository.swift; sourceTree = ""; }; - 9450A38E299E62620031BBEB /* UserAttributeRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAttributeRepository.swift; sourceTree = ""; }; - 9450A390299E9ABD0031BBEB /* UserAttributeEntity+Mapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserAttributeEntity+Mapper.swift"; sourceTree = ""; }; 9451F90928229A11000ACE3B /* OfflineViewController+ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OfflineViewController+ContextMenu.swift"; sourceTree = ""; }; + 945222FD2A98AB1700C9DFAF /* SearchDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SearchDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 945222FF2A98AB1700C9DFAF /* SearchDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchDemoApp.swift; sourceTree = ""; }; + 945223012A98AB1700C9DFAF /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 945339CC2A6014FA00363DAD /* FileLinkViewController+ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileLinkViewController+ContextMenu.swift"; sourceTree = ""; }; 94548FC5299D537C0050A9CC /* ChangeNameViewController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChangeNameViewController+Additions.swift"; sourceTree = ""; }; 9455CF522927F17200E6FB8F /* SnackBarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnackBarViewModel.swift; sourceTree = ""; }; @@ -3810,13 +3784,17 @@ 94614C742910554200503174 /* TransferNodeTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TransferNodeTableViewCell.xib; sourceTree = ""; }; 94624CF91F50484200D52504 /* MEGAExportRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGAExportRequestDelegate.h; path = API/Requests/MEGAExportRequestDelegate.h; sourceTree = ""; }; 94624CFA1F50484200D52504 /* MEGAExportRequestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MEGAExportRequestDelegate.m; path = API/Requests/MEGAExportRequestDelegate.m; sourceTree = ""; }; - 9464C2F428C65CDA00F8B082 /* CameraPositionEntity+Mapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraPositionEntity+Mapper.swift"; sourceTree = ""; }; 946544852A4446D50038D8FC /* PermissionAlertRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionAlertRouterTests.swift; sourceTree = ""; }; 9467F575299F9E6600A24D4A /* UserAttributeEntityMappingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAttributeEntityMappingTests.swift; sourceTree = ""; }; 946ACCC1299C099900E6C0F5 /* MEGAPresentation */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MEGAPresentation; sourceTree = ""; }; 946E783429C0F79800BB3FE6 /* SharedItemsSearchOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedItemsSearchOperation.swift; sourceTree = ""; }; 9470003829B77BCF00827E04 /* ActionWarningViewRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionWarningViewRouter.swift; sourceTree = ""; }; + 94712EEF2A9C8E4800B5F2C9 /* HomeSearchResultsProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeSearchResultsProviding.swift; sourceTree = ""; }; + 94712EF32A9C8F9400B5F2C9 /* HomeSearchResultsProvidingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeSearchResultsProvidingTests.swift; sourceTree = ""; }; + 94712EF92A9C9F5700B5F2C9 /* MockNodeDetailsUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNodeDetailsUseCase.swift; sourceTree = ""; }; + 94712EFC2A9C9FAD00B5F2C9 /* MockSearchFileUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSearchFileUseCase.swift; sourceTree = ""; }; 9472202B2A29E16600890CFB /* MyAccountHallViewController+TableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MyAccountHallViewController+TableViewDelegate.swift"; sourceTree = ""; }; + 947381B92A9626D000C4E8EC /* SearchResultsBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsBridge.swift; sourceTree = ""; }; 947755BA2A4C714800DAAEE5 /* ToolbarButtonsDisabler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonsDisabler.swift; sourceTree = ""; }; 947755BD2A4C74ED00DAAEE5 /* ToolbarButtonsDisablerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarButtonsDisablerTests.swift; sourceTree = ""; }; 9479F02428BF93C9008578BE /* DNDTurnOnOptionEntity+Mapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DNDTurnOnOptionEntity+Mapper.swift"; sourceTree = ""; }; @@ -3826,6 +3804,8 @@ 947C6CB42A5D40BE00CF929E /* MEGANavigationController+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MEGANavigationController+Additions.swift"; sourceTree = ""; }; 947C6CB92A5D417200CF929E /* InviteYourFriendsViewController+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "InviteYourFriendsViewController+Additions.swift"; sourceTree = ""; }; 947C6CC52A5D92DE00CF929E /* UIViewController+BackBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+BackBarButtonItem.swift"; sourceTree = ""; }; + 948176C42A93E9DC00E3A181 /* QuickAccessWidgetManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickAccessWidgetManagerTests.swift; sourceTree = ""; }; + 948425D72A94EF020024FF1F /* MainTabBarController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainTabBarController+Additions.swift"; sourceTree = ""; }; 9487BF3A29B66F2E00645D32 /* NodeActionRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeActionRepositoryTests.swift; sourceTree = ""; }; 949086861FD5482E002B12BD /* NSAttributedString+MNZCategory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+MNZCategory.h"; sourceTree = ""; }; 949086871FD5482E002B12BD /* NSAttributedString+MNZCategory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+MNZCategory.m"; sourceTree = ""; }; @@ -3943,7 +3923,6 @@ A81AD7371F3099F800CA4059 /* MEGAProcessAsset.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MEGAProcessAsset.h; sourceTree = ""; }; A81AD7381F3099F800CA4059 /* MEGAProcessAsset.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MEGAProcessAsset.m; sourceTree = ""; }; A81BD63227EDC4C100234902 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/ShortcutIntents.strings; sourceTree = ""; }; - A81BD63327EDC4C100234902 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; A81BD63427EDC4C200234902 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/InfoPlist.strings; sourceTree = ""; }; A81C6584254B2D3000105BF4 /* GiphySelectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiphySelectionViewController.swift; sourceTree = ""; }; A81C6585254B2D3000105BF4 /* GiphySelectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiphySelectionView.swift; sourceTree = ""; }; @@ -3956,7 +3935,6 @@ A81F9BB327EA023A00D47C0D /* CookieSettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieSettingsViewModelTests.swift; sourceTree = ""; }; A820DA3A1F0E5BDC00F1F832 /* MEGAGetThumbnailRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MEGAGetThumbnailRequestDelegate.h; path = API/Requests/MEGAGetThumbnailRequestDelegate.h; sourceTree = ""; }; A820DA3B1F0E5BDC00F1F832 /* MEGAGetThumbnailRequestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MEGAGetThumbnailRequestDelegate.m; path = API/Requests/MEGAGetThumbnailRequestDelegate.m; sourceTree = ""; }; - A821A58F2862257400DB7017 /* IntentPersonProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentPersonProvider.swift; sourceTree = ""; }; A827510F1E97AB6E007CD9E2 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; A82751111E97AB73007CD9E2 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; A82751131E97AB79007CD9E2 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; @@ -4040,8 +4018,6 @@ A88E7FE92549FD8A001B0A66 /* RecentsTableViewHeaderFooterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RecentsTableViewHeaderFooterView.m; sourceTree = ""; }; A88E7FEA2549FD8A001B0A66 /* RecentsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecentsViewController.h; sourceTree = ""; }; A88FF24929A38CA300CC51F4 /* QuickLookThumbnailing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookThumbnailing.framework; path = System/Library/Frameworks/QuickLookThumbnailing.framework; sourceTree = SDKROOT; }; - A8919421299F8E6300B2A225 /* NodeAttributeRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAttributeRepository.swift; sourceTree = ""; }; - A8919423299F90A700B2A225 /* NodeAttributeRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAttributeRepositoryTests.swift; sourceTree = ""; }; A89629731FACE78B00F02F7A /* MEGACallManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MEGACallManager.h; path = Chat/MEGACallManager.h; sourceTree = ""; }; A89629741FACE78B00F02F7A /* MEGACallManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MEGACallManager.m; path = Chat/MEGACallManager.m; sourceTree = ""; }; A89629761FACEAF800F02F7A /* MEGAProviderDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MEGAProviderDelegate.h; path = Chat/MEGAProviderDelegate.h; sourceTree = ""; }; @@ -4061,7 +4037,6 @@ A8B09DE829B0CBD600C922B9 /* PickerConstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerConstant.swift; 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 = ""; }; - A8BA146B29927784004389FE /* NodeActionRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeActionRepository.swift; sourceTree = ""; }; A8BAFCAA28B6380E00ED4259 /* OfflineFileEntity+Mapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OfflineFileEntity+Mapper.swift"; sourceTree = ""; }; A8BB95112813028F002449D7 /* SharedItemsTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SharedItemsTableViewCell.xib; sourceTree = ""; }; A8BB951F2816A006002449D7 /* SharedItemsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedItemsTableViewCell.swift; sourceTree = ""; }; @@ -4130,6 +4105,7 @@ B52B8C67297FE9A400981F47 /* RecentsViewController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RecentsViewController+Additions.swift"; sourceTree = ""; }; B52CB2332A7FA78A00F9C56C /* OfflineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineViewModel.swift; sourceTree = ""; }; B52E231C28A0D66F0018C3C3 /* FeatureFlagUseCase+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeatureFlagUseCase+Additions.swift"; sourceTree = ""; }; + B53B02BD2A96135200550A69 /* MEGAPurchase+PromotedPlan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MEGAPurchase+PromotedPlan.swift"; sourceTree = ""; }; B541CD2F2A6AAF53004B4CCC /* PlanSelectionSnackBarType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanSelectionSnackBarType.swift; sourceTree = ""; }; B547025128B8AFB6005F7F71 /* MEGAPhotoBrowserViewController+LiveText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MEGAPhotoBrowserViewController+LiveText.swift"; sourceTree = ""; }; B547C0E529FB716C0008BEF2 /* SortOrderType+Mapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SortOrderType+Mapper.swift"; sourceTree = ""; }; @@ -4169,25 +4145,6 @@ B5967A0729403AE500AD4E61 /* VerifyCredentialsViewController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VerifyCredentialsViewController+Additions.swift"; sourceTree = ""; }; B596D1292977DC33002C5094 /* NodeInfoViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeInfoViewModel.swift; sourceTree = ""; }; B596D15B2979280A002C5094 /* CloudDriveViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudDriveViewModel.swift; sourceTree = ""; }; - B5A99635282CF625008B8ADD /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = Base; path = iMEGA/Languages/Base.lproj/Localizable.stringsdict; sourceTree = SOURCE_ROOT; }; - B5A9963E282CF62F008B8ADD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = ../Languages/en.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A9963F282CF631008B8ADD /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ../Languages/ar.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A99640282CF633008B8ADD /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hans"; path = "../Languages/zh-Hans.lproj/Localizable.stringsdict"; sourceTree = ""; }; - B5A99641282CF637008B8ADD /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant"; path = "../Languages/zh-Hant.lproj/Localizable.stringsdict"; sourceTree = ""; }; - B5A99642282CF63A008B8ADD /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = ../Languages/nl.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A99643282CF63C008B8ADD /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = ../Languages/fr.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A99644282CF63D008B8ADD /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = de; path = ../Languages/de.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A99645282CF63F008B8ADD /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = ../Languages/id.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A99646282CF640008B8ADD /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = ../Languages/it.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A99647282CF642008B8ADD /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ../Languages/ja.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A99648282CF643008B8ADD /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = ../Languages/ko.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A99649282CF645008B8ADD /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pl; path = ../Languages/pl.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A9964A282CF646008B8ADD /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pt; path = ../Languages/pt.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A9964B282CF649008B8ADD /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ro; path = ../Languages/ro.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A9964C282CF64A008B8ADD /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ../Languages/ru.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A9964D282CF64C008B8ADD /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = ../Languages/es.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A9964E282CF64D008B8ADD /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = th; path = ../Languages/th.lproj/Localizable.stringsdict; sourceTree = ""; }; - B5A9964F282CF64F008B8ADD /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = vi; path = ../Languages/vi.lproj/Localizable.stringsdict; sourceTree = ""; }; B5AD33C429D136A500707496 /* UpgradeAccountViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpgradeAccountViewModel.swift; sourceTree = ""; }; B5B0E30527F1BCA700DB6146 /* WarningViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningViewModelTests.swift; sourceTree = ""; }; B5BEB0FD2907C5F5006A442D /* CameraUploadsTableViewController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraUploadsTableViewController+Additions.swift"; sourceTree = ""; }; @@ -4209,7 +4166,6 @@ B5E9D3FB28ACEB0800504F1F /* FeatureFlagViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureFlagViewModel.swift; sourceTree = ""; }; B5E9D41428ACF8CE00504F1F /* FeatureFlagViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagViewModelTests.swift; sourceTree = ""; }; B5EF0A7529F0EE2000B166B1 /* AccountPlanPurchaseRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountPlanPurchaseRepository.swift; sourceTree = ""; }; - B5EF0A8329F11E7400B166B1 /* AccountPlanEntity+Mapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountPlanEntity+Mapper.swift"; sourceTree = ""; }; B5FC636129E8F53400EE4C8E /* NodeCollectionViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeCollectionViewCellViewModel.swift; sourceTree = ""; }; B5FC636329E9283C00EE4C8E /* NodeCollectionViewCell+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NodeCollectionViewCell+Additions.swift"; sourceTree = ""; }; B5FC636829E9351100EE4C8E /* NodeCollectionViewCellViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeCollectionViewCellViewModelTests.swift; sourceTree = ""; }; @@ -4637,7 +4593,6 @@ BF53B77B274351F700B47CA0 /* MeetingAlreadyExistsAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingAlreadyExistsAlert.swift; sourceTree = ""; }; BF53B78B2745F92F00B47CA0 /* MEGAChatCall+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MEGAChatCall+Additions.swift"; sourceTree = ""; }; BF5455E82844377600544A53 /* EndCallDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndCallDialog.swift; sourceTree = ""; }; - BF5BC7732611687A00716CD6 /* UserInviteRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInviteRepository.swift; sourceTree = ""; }; BF5E336B2558BE2A0055395E /* ExplorerBaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplorerBaseViewController.swift; sourceTree = ""; }; BF5E33752558BE620055395E /* ExplorerToolbarConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplorerToolbarConfigurator.swift; sourceTree = ""; }; BF5E338F2558D2890055395E /* FileExplorerGridCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileExplorerGridCell.swift; sourceTree = ""; }; @@ -4650,9 +4605,9 @@ BF5E340D2559E2EC0055395E /* FilesExplorerContainerGridViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesExplorerContainerGridViewState.swift; sourceTree = ""; }; BF5E345E255A2E1A0055395E /* FilesExplorerGridSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesExplorerGridSource.swift; sourceTree = ""; }; BF5E3494255C8FD10055395E /* FilesExplorerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesExplorerViewController.swift; sourceTree = ""; }; + BF61494F2A9FF30000AB051A /* MEGAL10n */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MEGAL10n; sourceTree = ""; }; BF66CA7628B83ACF00DB686E /* HangOrEndCallViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HangOrEndCallViewModelTests.swift; sourceTree = ""; }; BF66DF152612C20200066F35 /* ChatRoomRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomRepository.swift; sourceTree = ""; }; - BF66DF352612CE3100066F35 /* InviteRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteRequestDelegate.swift; sourceTree = ""; }; BF66DFBD2613CC4500066F35 /* ChatRoomEntity+Mapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatRoomEntity+Mapper.swift"; sourceTree = ""; }; BF66DFC72613E6AC00066F35 /* MeetingFloatingPanelRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingFloatingPanelRouter.swift; sourceTree = ""; }; BF66DFD12613E6DC00066F35 /* MeetingFloatingPanelViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingFloatingPanelViewModel.swift; sourceTree = ""; }; @@ -4677,9 +4632,7 @@ BF6DF3692457A06300D612D0 /* AddToChatImageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToChatImageCell.swift; sourceTree = ""; }; BF6DF36A2457A06300D612D0 /* AddToChatImageCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AddToChatImageCell.xib; sourceTree = ""; }; BF6E8D2C25B636FB003A125A /* PSAViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PSAViewModelTests.swift; sourceTree = ""; }; - BF6E8DFE25BA556B003A125A /* MainTabBarController+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainTabBarController+Additions.swift"; sourceTree = ""; }; BF74E1E2253668E1001F663C /* HomeRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeRouter.swift; sourceTree = ""; }; - BF782D44269BA4BA0001D169 /* UserImageLoadErrorEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserImageLoadErrorEntity.swift; sourceTree = ""; }; BF7D43B3253CFF6F003DF1DA /* SDKNodesUpdateListenerRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKNodesUpdateListenerRepository.swift; sourceTree = ""; }; BF7D43BA253D0930003DF1DA /* Sequence+MEGANode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+MEGANode.swift"; sourceTree = ""; }; BF7D43D7253D31D7003DF1DA /* SDKTransferListenerRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKTransferListenerRepository.swift; sourceTree = ""; }; @@ -4708,8 +4661,6 @@ BF89C8A32947259B00710FD1 /* ChatRoomViewModel+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatRoomViewModel+Additions.swift"; sourceTree = ""; }; BF8B02EB28359EDD009AC794 /* callEnded.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = callEnded.wav; sourceTree = ""; }; BF8B02F22835A09E009AC794 /* MEGAProviderDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MEGAProviderDelegate.swift; path = Chat/MEGAProviderDelegate.swift; sourceTree = ""; }; - BF90C7632603FB42006061F8 /* UserImageRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserImageRepositoryProtocol.swift; sourceTree = ""; }; - BF90C77F2603FD4A006061F8 /* UserImageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserImageRepository.swift; sourceTree = ""; }; BF90C794260406FC006061F8 /* UserImageUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserImageUseCase.swift; sourceTree = ""; }; BF90C7A626040930006061F8 /* UserStoreRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserStoreRepository.swift; sourceTree = ""; }; BF923EC62A007DEB00C4D8B0 /* ScheduleMeetingCreationRecurrenceOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMeetingCreationRecurrenceOptionsView.swift; sourceTree = ""; }; @@ -4770,8 +4721,6 @@ BFAB113523CEA5C300BCE72D /* AlbumTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumTableViewCell.swift; sourceTree = ""; }; BFAB113623CEA5C300BCE72D /* AlbumTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AlbumTableViewCell.xib; sourceTree = ""; }; BFAB113723CEA5C300BCE72D /* AlbumsTableViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumsTableViewDataSource.swift; sourceTree = ""; }; - BFAC873825FEF26C009CA3F6 /* AudioSessionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSessionRepository.swift; sourceTree = ""; }; - BFAC87962600041C009CA3F6 /* CaptureDeviceRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaptureDeviceRepository.swift; sourceTree = ""; }; BFAD0F972A2978D2003F5F83 /* ScheduleMeetingCreationCustomOptionsRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMeetingCreationCustomOptionsRouter.swift; sourceTree = ""; }; BFAD0F992A297DEA003F5F83 /* ScheduleMeetingCreationCustomOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMeetingCreationCustomOptionsView.swift; sourceTree = ""; }; BFAD0F9B2A297DF3003F5F83 /* ScheduleMeetingCreationCustomOptionsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMeetingCreationCustomOptionsViewModel.swift; sourceTree = ""; }; @@ -4863,7 +4812,6 @@ BFD6535E25EDE8E500052DE8 /* MeetingParticipantInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingParticipantInfoViewController.swift; sourceTree = ""; }; BFD6557425F59EC600052DE8 /* CircularView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularView.swift; sourceTree = ""; }; BFD6564E25F6D22300052DE8 /* MeetingQuickActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingQuickActionView.swift; sourceTree = ""; }; - BFDE0EEA263A44BC00FC586E /* MockUserInviteUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserInviteUseCase.swift; sourceTree = ""; }; BFDEE879297118D1004B585D /* NotificationsTableViewController+Meetings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationsTableViewController+Meetings.swift"; sourceTree = ""; }; BFE3B9D228569B6B003949FF /* MeetingNoUserJoinedRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingNoUserJoinedRepository.swift; sourceTree = ""; }; BFE3B9D628569BAD003949FF /* MeetingNoUserJoinedRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingNoUserJoinedRepositoryProtocol.swift; sourceTree = ""; }; @@ -5012,14 +4960,6 @@ E83E10281E1582E8002F3E2E /* ChatStatusTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChatStatusTableViewController.h; sourceTree = ""; }; E83E10291E1582E8002F3E2E /* ChatStatusTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChatStatusTableViewController.m; sourceTree = ""; }; E84CDB001A7292C9003F4C77 /* Links.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Links.storyboard; sourceTree = ""; }; - E84F86FD1BB99DE200436A0D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; - E84F86FE1BB99E1700436A0D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; - E84F86FF1BB99E6A00436A0D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; - E84F87011BB99F5F00436A0D /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; - E84F87021BB99FA500436A0D /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; - E84F87041BB9A02800436A0D /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; - E84F87051BB9A07200436A0D /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; - E84F87091BB9A27600436A0D /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; lineEnding = 0; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.simpleColoring; }; E85044C41A6D84620064B675 /* FileLinkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileLinkViewController.h; sourceTree = ""; }; E85044C51A6D84620064B675 /* FileLinkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = FileLinkViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; E8534D491E15231C00911C40 /* ChatSettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChatSettingsTableViewController.h; sourceTree = ""; }; @@ -5030,16 +4970,13 @@ E879DFD91D12A31D009B74FD /* UIDevice+MNZCategory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIDevice+MNZCategory.m"; sourceTree = ""; }; E87C98341CC4CC1000E2B9E6 /* AdvancedTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AdvancedTableViewController.h; sourceTree = ""; }; E87C98351CC4CC1000E2B9E6 /* AdvancedTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AdvancedTableViewController.m; sourceTree = ""; }; - E8860CCF1C7724A00026ABBC /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; E889C9E51B5565C900ECEFDF /* MyAccount.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = MyAccount.storyboard; sourceTree = ""; }; E889C9E71B566EC400ECEFDF /* UsageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UsageViewController.h; sourceTree = ""; }; E889C9E81B566EC400ECEFDF /* UsageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = UsageViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; E88E6D7E1CF355FA00C9AD9E /* UIColor+MNZCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+MNZCategory.h"; sourceTree = ""; }; E88E6D7F1CF355FA00C9AD9E /* UIColor+MNZCategory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+MNZCategory.m"; sourceTree = ""; }; - E894271F1CCF8411008D470D /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = ""; }; E8B415041DE3237F00B9D206 /* GroupChatDetailsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GroupChatDetailsViewController.h; path = Chat/GroupChatDetailsViewController.h; sourceTree = ""; }; E8B415051DE3237F00B9D206 /* GroupChatDetailsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = GroupChatDetailsViewController.m; path = Chat/GroupChatDetailsViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; - E8B64EFB1BC6E48C005C726E /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; E8BB73651D77398A0067D89C /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; E8BB73661D7739960067D89C /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; E8BB73681D77399A0067D89C /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; @@ -5103,6 +5040,8 @@ B6061FEC2897928D0094087E /* MEGADomain in Frameworks */, B66BC70828A0F9CB00D4CB04 /* MEGADomainMock in Frameworks */, B6A01BAC289A61CA00B503FD /* MEGAFoundation in Frameworks */, + BF6149522A9FF33300AB051A /* MEGAL10n in Frameworks */, + BF6149542A9FF33300AB051A /* MEGAL10nObjc in Frameworks */, 9418DB292A4EE5FB002B0077 /* MEGAPermissions in Frameworks */, 946ACCC5299C0B0000E6C0F5 /* MEGAPresentation in Frameworks */, A891E8A529C1E09D0020072A /* MEGASdk in Frameworks */, @@ -5121,6 +5060,7 @@ BF0CB9362803AEAE00C21F11 /* SAMKeychain in Frameworks */, BFA275E327ED315E00E15B92 /* SDAVAssetExportSession in Frameworks */, BF1164B027F68EC500E5C5A4 /* SDWebImageWebPCoder in Frameworks */, + 948425E32A9504400024FF1F /* Search in Frameworks */, 940931FE2A56ED680093C169 /* Settings in Frameworks */, A8B51AE927F3565E0030F1EA /* WSTagsField in Frameworks */, ); @@ -5130,10 +5070,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BF29179D2A9D8DD800A59D12 /* ChatRepo in Frameworks */, A828E687286B318A004CED9E /* FirebaseCrashlytics in Frameworks */, A812CB3B2A4B0FB400F99382 /* MEGAChatSdk in Frameworks */, A86280BC291E8D510010D6E0 /* MEGADomain in Frameworks */, B6A01BB4289A644D00B503FD /* MEGAFoundation in Frameworks */, + BF61495A2A9FF35200AB051A /* MEGAL10n in Frameworks */, + BF61495C2A9FF35200AB051A /* MEGAL10nObjc in Frameworks */, 946ACCC3299C0ADD00E6C0F5 /* MEGAPresentation in Frameworks */, A891E8A929C1E0B00020072A /* MEGASdk in Frameworks */, B63D341C2898A9E90059DE66 /* MEGASwift in Frameworks */, @@ -5147,6 +5090,8 @@ files = ( A828E685286B317F004CED9E /* FirebaseCrashlytics in Frameworks */, B610447428979EA200AAECF4 /* MEGADomain in Frameworks */, + BF61496A2A9FF39500AB051A /* MEGAL10n in Frameworks */, + BF61496C2A9FF39500AB051A /* MEGAL10nObjc in Frameworks */, 946ACCC7299C0B3500E6C0F5 /* MEGAPresentation in Frameworks */, BF0CB9422803AF6000C21F11 /* SAMKeychain in Frameworks */, ); @@ -5157,9 +5102,11 @@ buildActionMask = 2147483647; files = ( A828E683286B3150004CED9E /* FirebaseCrashlytics in Frameworks */, - A803C8FB2955DB550056C9F0 /* MEGADomain in Frameworks */, + 940F41382A9FC40B00C2543F /* MEGAIntentDomain in Frameworks */, + BF6149562A9FF34400AB051A /* MEGAL10n in Frameworks */, + BF6149582A9FF34400AB051A /* MEGAL10nObjc in Frameworks */, 327717B529FB82C4004DE295 /* MEGAPresentation in Frameworks */, - 3241349729FB712B006E5310 /* MEGASdk in Frameworks */, + 94FD8A8B2AA0C6F300CD1C8B /* MEGARepo in Frameworks */, A8DF55682A5F0F6900CA33E5 /* MEGASDKRepo in Frameworks */, A803C8FD2955DB790056C9F0 /* SAMKeychain in Frameworks */, ); @@ -5176,6 +5123,8 @@ A812CB3D2A4B0FBE00F99382 /* MEGAChatSdk in Frameworks */, B610447228979E9B00AAECF4 /* MEGADomain in Frameworks */, B6A01BB0289A623C00B503FD /* MEGAFoundation in Frameworks */, + BF6149662A9FF38400AB051A /* MEGAL10n in Frameworks */, + BF6149682A9FF38400AB051A /* MEGAL10nObjc in Frameworks */, 946ACCC9299C0B5700E6C0F5 /* MEGAPresentation in Frameworks */, A891E8A729C1E0A70020072A /* MEGASdk in Frameworks */, B63D341A2898A9E20059DE66 /* MEGASwift in Frameworks */, @@ -5188,11 +5137,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 945222FA2A98AB1700C9DFAF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9452230E2A98AB6300C9DFAF /* Search in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A8399F3F296C40060062B6BC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( A8399FBB296DD5F10062B6BC /* MEGADomain in Frameworks */, + BF6149622A9FF37000AB051A /* MEGAL10n in Frameworks */, + BF6149642A9FF37000AB051A /* MEGAL10nObjc in Frameworks */, 94B6E8852A83F6410082D9B1 /* MEGAPickerFileProviderDomain in Frameworks */, 94B6E8882A83F8530082D9B1 /* MEGAPickerFileProviderRepo in Frameworks */, BFFC467A2A6F64A900A5DAA8 /* MEGAPresentation in Frameworks */, @@ -5206,6 +5165,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BF61495E2A9FF36200AB051A /* MEGAL10n in Frameworks */, + BF6149602A9FF36200AB051A /* MEGAL10nObjc in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5226,15 +5187,10 @@ children = ( 944AD0B028EF039700AB1E59 /* BackupsRepository.swift */, 8396AB4027BBDD5600C71291 /* ImportNodeRepository.swift */, - A8BA146B29927784004389FE /* NodeActionRepository.swift */, 943092F32913E26D0092B99A /* NodeActionsRepository.swift */, - A8919421299F8E6300B2A225 /* NodeAttributeRepository.swift */, 943092F6291438200092B99A /* NodeDataRepository.swift */, - 5D609287255BA0AE005FA3E4 /* NodeFavouriteActionRepository.swift */, 0D213E8D263A613500B43723 /* NodeRepository.swift */, - 0E32FFB1293E896A004BB11D /* NodeUpdateRepository.swift */, 943092F9291440010092B99A /* NodeValidationRepository.swift */, - 944DB355295EE64900CE4D83 /* RubbishBinRepository.swift */, 94485B5329B9F8BE006B919E /* SearchNodeRepository.swift */, ); path = Node; @@ -5306,6 +5262,14 @@ path = CopyrightWarning; sourceTree = ""; }; + 0EED79E62A9C0AFF00213201 /* Extensions */ = { + isa = PBXGroup; + children = ( + 0E2363B42A9EDDCC0019BBBE /* AlbumEntity+Analytics.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 1200A0E5286E4E2F00791A60 /* Repos */ = { isa = PBXGroup; children = ( @@ -5313,13 +5277,11 @@ B5CC2A1B299347BA00A966A0 /* AccountRepositoryTests.swift */, 12B1E5792874FFEF00D23A58 /* AnalyticsRepositoryTests.swift */, 940445B3298B044A00A5ECA1 /* APIEnvironmentRepositoryTests.swift */, + 25ED56802A9D867D0063864F /* DownloadFileRepositoryTests.swift */, 328FDCF22911FE2100E5990A /* FilesSearchRepositoryTests.swift */, 9487BF3A29B66F2E00645D32 /* NodeActionRepositoryTests.swift */, - A8919423299F90A700B2A225 /* NodeAttributeRepositoryTests.swift */, 94E37E5129D1A9B0009E396D /* NodeRepositoryTests.swift */, - 0E32FFA9293E8179004BB11D /* NodeUpdateRepositoryTests.swift */, 9449938E28BD312D001AE14B /* NodeValidationRepositoryTests.swift */, - 94417EAE296D825B007C9230 /* RubbishBinRepositoryTests.swift */, 0E6F57B3299DEB13005C279F /* SDKNodesUpdateListenerRepositoryTests.swift */, 94485B5529B9F8EC006B919E /* SearchNodeRepositoryTests.swift */, ); @@ -5356,6 +5318,7 @@ 9DD2E849295557E5000983A3 /* Alert */, 12397E1B2820C45800A30CCC /* Cell */, 12F23A5A284EB7C6007B9821 /* Data */, + 0EED79E62A9C0AFF00213201 /* Extensions */, 9D4B25312964D5A20017E51A /* AlbumContentPickerView.swift */, 9D94AF6B296532EE00C7FBAA /* AlbumContentPickerViewModel.swift */, 12397E202820C4A600A30CCC /* AlbumListView.swift */, @@ -5567,6 +5530,7 @@ B63AEB082511BD2B000B8517 /* MEGAData */, B6D0B364283DC2320020C64D /* MEGADataTests */, B654BF3724FF117700A3CBC8 /* MEGADomain */, + 945222FE2A98AB1700C9DFAF /* SearchDemo */, 418851EE1A66826800FDBA15 /* Frameworks */, 1373618B1A6664C300B740E8 /* Products */, 77AEB94223CDB9EA0047CEBF /* Recovered References */, @@ -5584,6 +5548,7 @@ 943F8B0F1EF26E9C00AFD89F /* MEGAShare.appex */, B6CED48B22B8AC70009C4861 /* MEGAUnitTests.xctest */, 83199F1125556CD900344531 /* MEGAWidgetExtension.appex */, + 945222FD2A98AB1700C9DFAF /* SearchDemo.app */, ); name = Products; sourceTree = ""; @@ -5618,8 +5583,6 @@ B67FFF9429D53D1400E830FA /* Release */, 1373618E1A6664C300B740E8 /* Info.plist */, E803CB661D5336F100F81A36 /* InfoPlist.strings */, - 415227411A692F8D00EC7BB6 /* Localizable.strings */, - B5A99634282CF625008B8ADD /* Localizable.stringsdict */, 1373618F1A6664C300B740E8 /* main.m */, A87FF3BF22F8111C00EFD95C /* MEGA-Bridging-Header.h */, 4122C0E71B27539F001CE833 /* MEGA-PrefixHeader.pch */, @@ -5729,6 +5692,7 @@ 24A6ADD82A81EBDA0035C7B0 /* AudioPlayerAllAudioAsPlaylistShiftStrategyTests.swift */, 24A6ADD52A81E87D0035C7B0 /* AudioPlayerDefaultPlaylistShiftStrategyTests.swift */, 24A6ADDB2A8222CE0035C7B0 /* AudioPlayerPlaylistShiftStrategyTestSpecs.swift */, + 246ACEB42AA2D9C5000BD2A8 /* AudioPlayerViewRouterNodeActionAdapterTests.swift */, 24F231582A5FB33A000BFBBA /* AudioPlayerViewRouterTests.swift */, ); path = AudioPlayerScenes; @@ -5776,6 +5740,33 @@ path = DataSource; sourceTree = ""; }; + 25D840672A9EE0C30026D288 /* PermissionAlertViewModifier */ = { + isa = PBXGroup; + children = ( + 25D8406B2A9EE2860026D288 /* PermissionAlertModel.swift */, + 25D840682A9EE0E50026D288 /* PermissionAlertViewModifier.swift */, + ); + path = PermissionAlertViewModifier; + sourceTree = ""; + }; + 25D8406E2A9EE8EB0026D288 /* AlertRouter */ = { + isa = PBXGroup; + children = ( + 943649DE2A4D83DE006CE8B8 /* AlertModel+Equatable.swift */, + 943649DD2A4D83DE006CE8B8 /* CustomModalModel+Equatable.swift */, + 946544852A4446D50038D8FC /* PermissionAlertRouterTests.swift */, + ); + path = AlertRouter; + sourceTree = ""; + }; + 25D8406F2A9EE90F0026D288 /* AlertViewModifier */ = { + isa = PBXGroup; + children = ( + 25D840702A9EE9330026D288 /* PermissionAlertModelTests.swift */, + ); + path = AlertViewModifier; + sourceTree = ""; + }; 2F7B67DA24AEDD6B008291D7 /* ReactionView */ = { isa = PBXGroup; children = ( @@ -6147,7 +6138,6 @@ 835223AE205956A700A47438 /* Cells */, 5B9F20112433330000F22FBA /* ContactLinkQR */, 83ECE1702488EFE600E9E264 /* ContactsGroups */, - 834AFD1F2498E7BF005DF37C /* ContactsPicker */, 5BA53FEA2462B27800D168B9 /* Invite Contact */, 83337CA821009476000F40A7 /* ItemList */, 831844B922C4D51500E4C401 /* Views */, @@ -6187,6 +6177,7 @@ 415226791A692ECC00EC7BB6 /* LoginViewController.h */, 4152267A1A692ECC00EC7BB6 /* LoginViewController.m */, E8E1D2D41AFA860E00A9BD5B /* Main.storyboard */, + 948425D72A94EF020024FF1F /* MainTabBarController+Additions.swift */, C451AB9525A4CDB700A5484B /* MainTabBarController+AudioPlayer.swift */, B6D995F7222CA42000A98BEF /* MainTabBarController+CameraUpload.h */, B6D995F8222CA42000A98BEF /* MainTabBarController+CameraUpload.m */, @@ -6505,7 +6496,6 @@ BFB463EC2433FD9C00FB90B9 /* Date+Additions.swift */, B6A01BA7289A5E9200B503FD /* FileManager+Additions.swift */, B6A01BA6289A5E9200B503FD /* FolderContentStat.swift */, - BF6E8DFE25BA556B003A125A /* MainTabBarController+Additions.swift */, BF53B78B2745F92F00B47CA0 /* MEGAChatCall+Additions.swift */, BF0ACD63240C87A80002101E /* MEGAChatListItem+Additions.swift */, BF30AB9A24A05B7A00862853 /* MEGAChatMessage+Additions.swift */, @@ -6649,6 +6639,7 @@ 2F3083BB2428169E0058DF91 /* Chat.storyboard */, 5B39DAA01EF916D900BC2319 /* ChatAttachedContactsViewController.h */, 5B39DAA11EF916D900BC2319 /* ChatAttachedContactsViewController.m */, + 0EE990932AA55D9300D43714 /* ChatAttachedNodesViewController+Additions.swift */, 5B20F4A01EFA9629007E0A34 /* ChatAttachedNodesViewController.h */, 5B20F4A11EFA9629007E0A34 /* ChatAttachedNodesViewController.m */, BF943EA42355449A001C18E0 /* ChatNotificationControl.swift */, @@ -7068,7 +7059,6 @@ 83DE9D2528EC823400F24DF4 /* ChatRepository.swift */, BF66DF152612C20200066F35 /* ChatRoomRepository.swift */, 5B98BE1227AA9FF700426B97 /* ExportChatMessagesRepository.swift */, - 5BFA962F252E1119003653EB /* ManageChatHistoryRepository.swift */, 836A04DD29813E62009C9405 /* ScheduledMeetingRepository.swift */, ); path = Chat; @@ -7147,6 +7137,7 @@ 5D166B752521952F004F4F4A /* HomeSearch */ = { isa = PBXGroup; children = ( + 94712EEE2A9C8E3400B5F2C9 /* Provider */, 5D166B80252197DF004F4F4A /* Router */, 5D166B8F2521992D004F4F4A /* View */, 5D166B8E25219910004F4F4A /* ViewModel */, @@ -7241,8 +7232,8 @@ 5D1E3C182531F2BE00BD7A07 /* Search */ = { isa = PBXGroup; children = ( + 94712EFB2A9C9F9900B5F2C9 /* SearchFileUseCase */, 5D11B73D2531F1FD00FD4143 /* SearchFileHistoryUseCase.swift */, - 5D5DE4F72511F88900F2B4FD /* SearchFileUseCase.swift */, ); path = Search; sourceTree = ""; @@ -7403,6 +7394,7 @@ B58050C02A823A6100D377D1 /* MockABTestProvider.swift */, B5E411AA28AB740F00D05719 /* MockFeatureFlagProvider.swift */, 32D167A92942DA59008C5ED6 /* MockSlideShowDataSource.swift */, + 25ED56692A98699E0063864F /* MockTransferWidgetResponder.swift */, 5D33D673250DBA3900D74666 /* StoreUser.swift */, 5D33D675250DBAAB00D74666 /* StoreUserClient.swift */, ); @@ -7496,10 +7488,10 @@ 5D5DE4F62511F86000F2B4FD /* Node */ = { isa = PBXGroup; children = ( + 94712EF72A9C9F4000B5F2C9 /* NodeDetails */, 5D1E3C182531F2BE00BD7A07 /* Search */, BFEA015B256B4E8A00646606 /* FilesDownloadUseCase.swift */, BFEAFF7B2565D57300646606 /* NodeClipboardOperationUseCase.swift */, - 5DF44CCB251369E000E9F0BC /* NodeDetailsUseCase.swift */, 5D6092A5255D609F005FA3E4 /* NodeLabelActionUseCase.swift */, B678D6D72714266300BBA4E1 /* NodeThumbnailHomeUseCase.swift */, ); @@ -7585,37 +7577,18 @@ path = Foundation; sourceTree = ""; }; - 5D8303B8256262250082CA18 /* Banner */ = { - isa = PBXGroup; - children = ( - 5D8303BB256262350082CA18 /* BannerRepository.swift */, - ); - path = Banner; - sourceTree = ""; - }; 5D8303DF256266C00082CA18 /* Error */ = { isa = PBXGroup; children = ( - 5D8303F4256367D60082CA18 /* BannerErrorEntity.swift */, 5D8303D1256266A80082CA18 /* GeneralDomainError.swift */, 5D60928F255BC2B6005FA3E4 /* SDKError.swift */, ); path = Error; sourceTree = ""; }; - 5D83041025638DA70082CA18 /* EntityMapping */ = { - isa = PBXGroup; - children = ( - 5D83041F25638DE70082CA18 /* MEGABanner.swift */, - 5D83041125638DC40082CA18 /* MEGABannerList.swift */, - ); - path = EntityMapping; - sourceTree = ""; - }; 5D83042D256392220082CA18 /* Banner */ = { isa = PBXGroup; children = ( - 5D83042E256392370082CA18 /* UserBannerUseCase.swift */, ); path = Banner; sourceTree = ""; @@ -7635,7 +7608,6 @@ isa = PBXGroup; children = ( 5D075D00251862800067D363 /* DataStore */, - 5D83041025638DA70082CA18 /* EntityMapping */, 5DF249E024F3965F003FED76 /* FileSystem */, 5DC016DC24F286D500BE309A /* SDK */, 5D3CD8A22531819200E3B459 /* UserDefault */, @@ -7652,6 +7624,7 @@ 5D166B752521952F004F4F4A /* HomeSearch */, 5D8C5AB624F0A39300EDD7A9 /* HomeLocalisation.swift */, 5DA32DB924EA27DC0075B613 /* HomeScreenFactory.swift */, + 947381B92A9626D000C4E8EC /* SearchResultsBridge.swift */, ); path = Scenes; sourceTree = ""; @@ -7712,7 +7685,6 @@ isa = PBXGroup; children = ( 5D8303DF256266C00082CA18 /* Error */, - 5D8303E6256270800082CA18 /* BannerEntity.swift */, 5D667EEB24ECF9400092474D /* StoreUser.swift */, ); path = Domain; @@ -7768,7 +7740,6 @@ 5DC016DC24F286D500BE309A /* SDK */ = { isa = PBXGroup; children = ( - 5D8303B8256262250082CA18 /* Banner */, 5D94D70D2513690700B103EC /* Node */, 5D6D0F552527044D002A777D /* SDKAvatarClient.swift */, BFEAFF712565D35400646606 /* SDKNodeClipboardOperationRepository.swift */, @@ -7782,8 +7753,6 @@ 5DD4B350257DFE9300A01306 /* RequestDelegate */ = { isa = PBXGroup; children = ( - BF66DF352612CE3100066F35 /* InviteRequestDelegate.swift */, - 5DF0E3CF2563A8540052CC95 /* MEGAResultMappingRequestDelegate.swift */, 5DF0E3D02563A8540052CC95 /* MEGAResultRequestDelegate.swift */, ); path = RequestDelegate; @@ -7835,6 +7804,7 @@ isa = PBXGroup; children = ( 5B9B992F26CD6953006FFBDD /* Favourites */, + 94712EF22A9C8F7B00B5F2C9 /* Search */, 9961E7242A282FBA004FB05F /* Videos */, 5DFC8DC424ECF37800DDA006 /* MEGAavatarGeneratingUseCaseTests.swift */, 5D667EEF24ED03340092474D /* MEGAavatarLoadingUseCaseTests.swift */, @@ -8004,6 +7974,7 @@ 77CAAADF202C58E4004B16ED /* MEGAPhotoBrowserPickerViewController.m */, BF49DEE62818A5BC00DE3742 /* MEGAPhotoBrowserViewController+Additions.swift */, B547025128B8AFB6005F7F71 /* MEGAPhotoBrowserViewController+LiveText.swift */, + 2592C2A12AA00D3100F902C8 /* MEGAPhotoBrowserViewController+SnackBarPresenting.swift */, 77D9A56A2023547900B48470 /* MEGAPhotoBrowserViewController.h */, 77D9A56B2023547900B48470 /* MEGAPhotoBrowserViewController.m */, 77D9A56D202355F800B48470 /* PhotoBrowser.storyboard */, @@ -8016,7 +7987,6 @@ isa = PBXGroup; children = ( 83BD329725DD3D4000F8B91D /* FavouriteItemsRepository.swift */, - 830C268025DFC78D00E9B56E /* FavouriteNodesRepository.swift */, ); path = Favourites; sourceTree = ""; @@ -8308,17 +8278,6 @@ path = ActiveCall; sourceTree = ""; }; - 834AFD1F2498E7BF005DF37C /* ContactsPicker */ = { - isa = PBXGroup; - children = ( - 834AFD222498E925005DF37C /* ContactsPicker.storyboard */, - 834AFD202498E7E1005DF37C /* ContactsPickerViewController.swift */, - 835AD14824A0F438009BC85A /* DeviceContactsManager.swift */, - 834AFD242498F637005DF37C /* DeviceContactTableViewCell.swift */, - ); - path = ContactsPicker; - sourceTree = ""; - }; 834D867D29197E5800CECEC0 /* ChatRoomAvatar */ = { isa = PBXGroup; children = ( @@ -8682,22 +8641,11 @@ 8396FBC525D6894D00EB50FD /* User */ = { isa = PBXGroup; children = ( - 9450A38E299E62620031BBEB /* UserAttributeRepository.swift */, - BF90C77F2603FD4A006061F8 /* UserImageRepository.swift */, - BF5BC7732611687A00716CD6 /* UserInviteRepository.swift */, BF90C7A626040930006061F8 /* UserStoreRepository.swift */, ); path = User; sourceTree = ""; }; - 8396FBE225D68E4C00EB50FD /* User */ = { - isa = PBXGroup; - children = ( - BF90C7632603FB42006061F8 /* UserImageRepositoryProtocol.swift */, - ); - path = User; - sourceTree = ""; - }; 839999D7264AE4E800520F38 /* OptionsMenu */ = { isa = PBXGroup; children = ( @@ -8840,7 +8788,6 @@ 83C7C85027EDE80200CFB5E6 /* PhotosLibrary */ = { isa = PBXGroup; children = ( - 83C7C85127EDE81F00CFB5E6 /* PhotosLibraryRepository.swift */, ); path = PhotosLibrary; sourceTree = ""; @@ -8935,7 +8882,6 @@ BFAD0FA32A299216003F5F83 /* ScheduleMeetingCreationWeeklyCustomOptionsView.swift */, BFB889962A15D9F500FC456A /* ScheduleMeetingMonthlyRecurrenceFootnoteView.swift */, 83DBDDE429D730DA00CF433B /* TextDescriptionView.swift */, - 83DBDDE229D730C800CF433B /* TextFieldView.swift */, ); path = Views; sourceTree = ""; @@ -9127,6 +9073,15 @@ path = SharedItems; sourceTree = ""; }; + 945222FE2A98AB1700C9DFAF /* SearchDemo */ = { + isa = PBXGroup; + children = ( + 945223012A98AB1700C9DFAF /* ContentView.swift */, + 945222FF2A98AB1700C9DFAF /* SearchDemoApp.swift */, + ); + path = SearchDemo; + sourceTree = ""; + }; 945339CF2A60150500363DAD /* Folder */ = { isa = PBXGroup; children = ( @@ -9187,12 +9142,53 @@ path = SortOrder; sourceTree = ""; }; + 94712EEE2A9C8E3400B5F2C9 /* Provider */ = { + isa = PBXGroup; + children = ( + 94712EEF2A9C8E4800B5F2C9 /* HomeSearchResultsProviding.swift */, + ); + path = Provider; + sourceTree = ""; + }; + 94712EF22A9C8F7B00B5F2C9 /* Search */ = { + isa = PBXGroup; + children = ( + 94712EF32A9C8F9400B5F2C9 /* HomeSearchResultsProvidingTests.swift */, + ); + path = Search; + sourceTree = ""; + }; + 94712EF72A9C9F4000B5F2C9 /* NodeDetails */ = { + isa = PBXGroup; + children = ( + 94712EF92A9C9F5700B5F2C9 /* MockNodeDetailsUseCase.swift */, + 5DF44CCB251369E000E9F0BC /* NodeDetailsUseCase.swift */, + ); + path = NodeDetails; + sourceTree = ""; + }; + 94712EFB2A9C9F9900B5F2C9 /* SearchFileUseCase */ = { + isa = PBXGroup; + children = ( + 94712EFC2A9C9FAD00B5F2C9 /* MockSearchFileUseCase.swift */, + 5D5DE4F72511F88900F2B4FD /* SearchFileUseCase.swift */, + ); + path = SearchFileUseCase; + sourceTree = ""; + }; + 948176C32A93E9BC00E3A181 /* Widget */ = { + isa = PBXGroup; + children = ( + 948176C42A93E9DC00E3A181 /* QuickAccessWidgetManagerTests.swift */, + ); + path = Widget; + sourceTree = ""; + }; 9495818C2A43295F0071573B /* Permissions */ = { isa = PBXGroup; children = ( - 943649DE2A4D83DE006CE8B8 /* AlertModel+Equatable.swift */, - 943649DD2A4D83DE006CE8B8 /* CustomModalModel+Equatable.swift */, - 946544852A4446D50038D8FC /* PermissionAlertRouterTests.swift */, + 25D8406E2A9EE8EB0026D288 /* AlertRouter */, + 25D8406F2A9EE90F0026D288 /* AlertViewModifier */, ); path = Permissions; sourceTree = ""; @@ -9467,7 +9463,7 @@ children = ( BF66E036261420A000066F35 /* CallEntity+Mapper.swift */, 838B976726B1615F000E480E /* CallParticipantEntity+Mapping.swift */, - 9464C2F428C65CDA00F8B082 /* CameraPositionEntity+Mapper.swift */, + 838838702A97769300148640 /* WaitingRoomEntity+Mapper.swift */, ); path = Call; sourceTree = ""; @@ -9499,13 +9495,6 @@ path = Offline; sourceTree = ""; }; - A859043629F2D1AC00632F0C /* Recent */ = { - isa = PBXGroup; - children = ( - ); - path = Recent; - sourceTree = ""; - }; A861B8332907F5DB00AB06D4 /* Features */ = { isa = PBXGroup; children = ( @@ -9585,7 +9574,6 @@ isa = PBXGroup; children = ( A8851064292D251200E92A54 /* IntentHandler+StartCall.swift */, - A821A58F2862257400DB7017 /* IntentPersonProvider.swift */, ); path = StartCall; sourceTree = ""; @@ -9946,21 +9934,12 @@ B5EF0A6829F0ECF900B166B1 /* Upgrade Account */ = { isa = PBXGroup; children = ( - B5EF0A6F29F0ED9700B166B1 /* Data Model */, B5EF0A7229F0EDF100B166B1 /* Repository */, B59580A32A1478AE00E5FC1C /* Views */, ); path = "Upgrade Account"; sourceTree = ""; }; - B5EF0A6F29F0ED9700B166B1 /* Data Model */ = { - isa = PBXGroup; - children = ( - B5EF0A8329F11E7400B166B1 /* AccountPlanEntity+Mapper.swift */, - ); - path = "Data Model"; - sourceTree = ""; - }; B5EF0A7229F0EDF100B166B1 /* Repository */ = { isa = PBXGroup; children = ( @@ -9973,6 +9952,7 @@ B5EF0A7329F0EDF900B166B1 /* MEGAPurchase */ = { isa = PBXGroup; children = ( + B53B02BD2A96135200550A69 /* MEGAPurchase+PromotedPlan.swift */, 41D4080E1B8F240B0001F8BE /* MEGAPurchase.h */, 41D4080F1B8F240B0001F8BE /* MEGAPurchase.m */, ); @@ -9994,6 +9974,7 @@ B6FDEF7A2A204E8300276DD6 /* DataSource */, B6FDEF782A204E7700276DD6 /* Domain */, B6FDEF772A204E6700276DD6 /* Infrastracture */, + BFD818C42A9D462500C30B71 /* Localization */, B6FDEF7C2A204E9600276DD6 /* Presentation */, B6FDEF792A204E7D00276DD6 /* Repository */, B6FDEF7B2A204E9000276DD6 /* UI */, @@ -10049,6 +10030,7 @@ B639952A2898CBA400EA88F1 /* SwiftUI */ = { isa = PBXGroup; children = ( + 25D840672A9EE0C30026D288 /* PermissionAlertViewModifier */, B63995322898CBEB00EA88F1 /* WarningView */, ); path = SwiftUI; @@ -10292,8 +10274,6 @@ 0D7E5ED926322BFB00C5CB2A /* NodeEntityMapping */, A859043529F2D1A000632F0C /* Offline */, A859043329F2D14300632F0C /* Preference */, - A859043629F2D1AC00632F0C /* Recent */, - B6FB5F8029A321130023BA6A /* User */, ); path = "Model Mapping"; sourceTree = ""; @@ -10387,7 +10367,6 @@ isa = PBXGroup; children = ( 838A7B7A25D5679100767DB0 /* Calls */, - 8396FBE225D68E4C00EB50FD /* User */, ); path = RepositoryProtocol; sourceTree = ""; @@ -10764,6 +10743,7 @@ 83B5E87527F6F8300073C870 /* Transfers */, B63AEB16251203F5000B8517 /* Utils */, B5B0E30427F1BC1100DB6146 /* Warning */, + 948176C32A93E9BC00E3A181 /* Widget */, B6CED48F22B8AC70009C4861 /* Info.plist */, B67B18EF25CB80690022D6C0 /* MEGA.xctestplan */, ); @@ -11039,14 +11019,6 @@ path = "Photo Selection"; sourceTree = ""; }; - B6FB5F8029A321130023BA6A /* User */ = { - isa = PBXGroup; - children = ( - 9450A390299E9ABD0031BBEB /* UserAttributeEntity+Mapper.swift */, - ); - path = User; - sourceTree = ""; - }; B6FD6510268D7EA500988A17 /* UseCase */ = { isa = PBXGroup; children = ( @@ -11074,6 +11046,7 @@ children = ( 16E14DD22A4ADE4700ED3AD5 /* MEGAAnalyticsDomain */, B6061FE82897909C0094087E /* MEGADomain */, + 940F41362A9ED39200C2543F /* MEGAIntentDomain */, 944917CC2A83C044006234D9 /* MEGAPickerFileProviderDomain */, ); path = Domain; @@ -11245,7 +11218,6 @@ children = ( BFBB88D428C6D84100E8B482 /* AllowNonHostToAddParticipantsErrorEntity.swift */, 125E606D285A87C2003C7BB5 /* SearchResultErrorEntity.swift */, - BF782D44269BA4BA0001D169 /* UserImageLoadErrorEntity.swift */, 99F300972A7A2DDF0087AE7E /* WaitingRoomErrorEntity.swift */, ); path = Error; @@ -11316,7 +11288,6 @@ 83669E8B2987BF6B003E8F74 /* MockScheduledMeetingUseCase.swift */, BFCA570E2A5525DD0033BEE2 /* MockScheduleMeetingViewConfiguration.swift */, BF8895C92630F4B3002A18CA /* MockUserImageUseCase.swift */, - BFDE0EEA263A44BC00FC586E /* MockUserInviteUseCase.swift */, 99FF30E32A8F3241006CE3DB /* ScheduleMeetingViewModel+Init.swift */, BFB889922A1598DB00FC456A /* WaitingRoomViewModel+Additions.swift */, ); @@ -11503,8 +11474,7 @@ BFAC873525FEF1F3009CA3F6 /* AudioVideo */ = { isa = PBXGroup; children = ( - BFAC873825FEF26C009CA3F6 /* AudioSessionRepository.swift */, - BFAC87962600041C009CA3F6 /* CaptureDeviceRepository.swift */, + 9428430C2AA7CFDD00278048 /* AudioSessionRepository.swift */, ); path = AudioVideo; sourceTree = ""; @@ -11603,6 +11573,14 @@ path = Utils; sourceTree = ""; }; + BFD818C42A9D462500C30B71 /* Localization */ = { + isa = PBXGroup; + children = ( + BF61494F2A9FF30000AB051A /* MEGAL10n */, + ); + path = Localization; + sourceTree = ""; + }; BFE556F427E183B40028EF1F /* SDK */ = { isa = PBXGroup; children = ( @@ -11942,6 +11920,7 @@ C49F8620255ABEA80074A054 /* AudioPlayerViewController.swift */, C49F8630255AC0310074A054 /* AudioPlayerViewModel.swift */, C49F8628255AC0200074A054 /* AudioPlayerViewRouter.swift */, + 246ACEB82AA2DAEE000BD2A8 /* AudioPlayerViewRouterNodeActionAdapter.swift */, ); path = AudioPlayerScene; sourceTree = ""; @@ -12188,6 +12167,9 @@ A8BC1C322A600F8D001B30F0 /* MEGASDKRepoMock */, 94A220172A696502004130C4 /* MEGAAnalyticsiOS */, A85CF69C2A7180A90018FEF1 /* ChatRepo */, + 948425E22A9504400024FF1F /* Search */, + BF6149512A9FF33300AB051A /* MEGAL10n */, + BF6149532A9FF33300AB051A /* MEGAL10nObjc */, ); productName = iMEGA; productReference = 1373618A1A6664C300B740E8 /* MEGA.app */; @@ -12217,6 +12199,9 @@ B63D341B2898A9E90059DE66 /* MEGASwift */, BF0CB93F2803AF5500C21F11 /* SAMKeychain */, A812CB3A2A4B0FB400F99382 /* MEGAChatSdk */, + BF29179C2A9D8DD800A59D12 /* ChatRepo */, + BF6149592A9FF35200AB051A /* MEGAL10n */, + BF61495B2A9FF35200AB051A /* MEGAL10nObjc */, ); productName = MEGANotifications; productReference = 7794AED4231E4E0A0039C1D2 /* MEGANotifications.appex */; @@ -12242,6 +12227,8 @@ B610447328979EA200AAECF4 /* MEGADomain */, 946ACCC6299C0B3500E6C0F5 /* MEGAPresentation */, BF0CB9412803AF6000C21F11 /* SAMKeychain */, + BF6149692A9FF39500AB051A /* MEGAL10n */, + BF61496B2A9FF39500AB051A /* MEGAL10nObjc */, ); productName = MEGAWidgetExtension; productReference = 83199F1125556CD900344531 /* MEGAWidgetExtension.appex */; @@ -12264,11 +12251,13 @@ name = MEGAIntent; packageProductDependencies = ( A828E682286B3150004CED9E /* FirebaseCrashlytics */, - A803C8FA2955DB550056C9F0 /* MEGADomain */, A803C8FC2955DB790056C9F0 /* SAMKeychain */, - 3241349629FB712B006E5310 /* MEGASdk */, 327717B429FB82C4004DE295 /* MEGAPresentation */, A8DF55672A5F0F6900CA33E5 /* MEGASDKRepo */, + 940F41372A9FC40B00C2543F /* MEGAIntentDomain */, + 94FD8A8A2AA0C6F300CD1C8B /* MEGARepo */, + BF6149552A9FF34400AB051A /* MEGAL10n */, + BF6149572A9FF34400AB051A /* MEGAL10nObjc */, ); productName = MEGAIntent; productReference = 831B490E256FED6F0086002B /* MEGAIntent.appex */; @@ -12305,11 +12294,33 @@ BF1164B727F6908500E5C5A4 /* SDWebImageWebPCoder */, A812CB3C2A4B0FBE00F99382 /* MEGAChatSdk */, A855D23D2A796634001D2168 /* ChatRepo */, + BF6149652A9FF38400AB051A /* MEGAL10n */, + BF6149672A9FF38400AB051A /* MEGAL10nObjc */, ); productName = MEGAShare; productReference = 943F8B0F1EF26E9C00AFD89F /* MEGAShare.appex */; productType = "com.apple.product-type.app-extension"; }; + 945222FC2A98AB1700C9DFAF /* SearchDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9452230B2A98AB1900C9DFAF /* Build configuration list for PBXNativeTarget "SearchDemo" */; + buildPhases = ( + 945222F92A98AB1700C9DFAF /* Sources */, + 945222FA2A98AB1700C9DFAF /* Frameworks */, + 945222FB2A98AB1700C9DFAF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SearchDemo; + packageProductDependencies = ( + 9452230D2A98AB6300C9DFAF /* Search */, + ); + productName = SearchDemo; + productReference = 945222FD2A98AB1700C9DFAF /* SearchDemo.app */; + productType = "com.apple.product-type.application"; + }; A8399F41296C40060062B6BC /* MEGAPickerFileProvider */ = { isa = PBXNativeTarget; buildConfigurationList = A8399F51296C40090062B6BC /* Build configuration list for PBXNativeTarget "MEGAPickerFileProvider" */; @@ -12333,6 +12344,8 @@ BFFC46792A6F64A900A5DAA8 /* MEGAPresentation */, 94B6E8842A83F6410082D9B1 /* MEGAPickerFileProviderDomain */, 94B6E8872A83F8530082D9B1 /* MEGAPickerFileProviderRepo */, + BF6149612A9FF37000AB051A /* MEGAL10n */, + BF6149632A9FF37000AB051A /* MEGAL10nObjc */, ); productName = MEGAPickerFileProvider; productReference = A8399F42296C40060062B6BC /* MEGAPickerFileProvider.appex */; @@ -12353,6 +12366,10 @@ 946336DA2A8ABA23005C58D9 /* PBXTargetDependency */, ); name = MEGAPicker; + packageProductDependencies = ( + BF61495D2A9FF36200AB051A /* MEGAL10n */, + BF61495F2A9FF36200AB051A /* MEGAL10nObjc */, + ); productName = MEGAPicker; productReference = A8399F59296C43F80062B6BC /* MEGAPicker.appex */; productType = "com.apple.product-type.app-extension"; @@ -12386,7 +12403,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1420; + LastSwiftUpdateCheck = 1430; LastUpgradeCheck = 1430; ORGANIZATIONNAME = MEGA; TargetAttributes = { @@ -12453,6 +12470,9 @@ }; }; }; + 945222FC2A98AB1700C9DFAF = { + CreatedOnToolsVersion = 14.3; + }; A8399F41296C40060062B6BC = { CreatedOnToolsVersion = 14.2; }; @@ -12530,6 +12550,7 @@ 943F8B0E1EF26E9C00AFD89F /* MEGAShare */, B6CED48A22B8AC70009C4861 /* MEGAUnitTests */, 83199F1025556CD900344531 /* MEGAWidgetExtension */, + 945222FC2A98AB1700C9DFAF /* SearchDemo */, ); }; /* End PBXProject section */ @@ -12591,7 +12612,6 @@ 5B9F2010243332D200F22FBA /* ContactLinkQR.storyboard in Resources */, 415226901A692ECC00EC7BB6 /* Contacts.storyboard in Resources */, 83ECE1742488F23900E9E264 /* ContactsGroups.storyboard in Resources */, - 834AFD232498E925005DF37C /* ContactsPicker.storyboard in Resources */, 5B86D7C5248957E100B7888D /* ContactsTableViewHeader.xib in Resources */, 5B366C4225767D2E00FD3806 /* CookieSettings.storyboard in Resources */, A819B3421EAFA844004592F9 /* CustomModalAlertViewController.xib in Resources */, @@ -12633,8 +12653,6 @@ 41D77EA91A6E8E6300818BE8 /* LaunchScreen.xib in Resources */, E84CDB011A7292C9003F4C77 /* Links.storyboard in Resources */, 2F528CAE24739053006DE5C2 /* LoadingMessageReusableView.xib in Resources */, - 4152273F1A692F8D00EC7BB6 /* Localizable.strings in Resources */, - B5A99638282CF625008B8ADD /* Localizable.stringsdict in Resources */, 41A72F1F1C22EA1600516603 /* LTHPasscodeViewController.bundle in Resources */, E8E1D2D51AFA860E00A9BD5B /* Main.storyboard in Resources */, B69AF65F28A1E5190022B094 /* ManageChatHistory.storyboard in Resources */, @@ -12681,6 +12699,7 @@ A88E7FEB2549FD8A001B0A66 /* RecentsTableViewHeaderView.xib in Resources */, 839B9FCF283D2DCA001A49DF /* reconnecting.wav in Resources */, BFC214812547C04800AD648A /* RichPreviewContentView.xib in Resources */, + 94328FB82A9672B7005B94EB /* Search in Resources */, 5D79EDD5251C71B500C0307A /* SearchHintTableViewCell.xib in Resources */, 5D4244E1251305BD00EE544D /* SearchResultFileTableViewCell.xib in Resources */, 5B82814A1E7BE8300079C1BB /* SelectableTableViewCell.xib in Resources */, @@ -12712,8 +12731,6 @@ files = ( B5925B7D279115FA00170CD6 /* Colors.xcassets in Resources */, 6DD7B2382A776313007EBB20 /* Images.xcassets in Resources */, - 77BE2FEF24000CB600514DB8 /* Localizable.strings in Resources */, - B5A9963B282CF625008B8ADD /* Localizable.stringsdict in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -12724,8 +12741,6 @@ 83422B28255984C300D46907 /* Assets.xcassets in Resources */, B5925B7E279115FA00170CD6 /* Colors.xcassets in Resources */, 6DD7B23A2A776313007EBB20 /* Images.xcassets in Resources */, - 837DC5592565605100CA289E /* Localizable.strings in Resources */, - B5A9963C282CF625008B8ADD /* Localizable.stringsdict in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -12736,8 +12751,6 @@ A882981E29F1668E00DEDE98 /* AppIntentVocabulary.plist in Resources */, B5925B7F279115FA00170CD6 /* Colors.xcassets in Resources */, 6DD7B2372A776313007EBB20 /* Images.xcassets in Resources */, - 8314150E256FFF5300D48BE3 /* Localizable.strings in Resources */, - B5A9963D282CF625008B8ADD /* Localizable.stringsdict in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -12755,8 +12768,6 @@ 6DD7B2392A776313007EBB20 /* Images.xcassets in Resources */, 5B45D2C2260A0FA600DDD076 /* ItemListViewController.storyboard in Resources */, 943F8B3D1EF2888000AFD89F /* Launch.storyboard in Resources */, - 941135651F011C8400D33428 /* Localizable.strings in Resources */, - B5A9963A282CF625008B8ADD /* Localizable.stringsdict in Resources */, 9411355B1F01006E00D33428 /* LTHPasscodeViewController.bundle in Resources */, 77E70DB82475760F00E74806 /* MegaAvatarView.xib in Resources */, 94598D751F0CD53A00B57144 /* Share.storyboard in Resources */, @@ -12764,6 +12775,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 945222FB2A98AB1700C9DFAF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; A8399F40296C40060062B6BC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -12775,7 +12793,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - A8A3D3742980383300037036 /* Localizable.strings in Resources */, A8399F5F296C43F90062B6BC /* MainInterface.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -12799,6 +12816,7 @@ inputFileListPaths = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Run Script: Automatically sets SDK and chat SDK commit hashes"; outputFileListPaths = ( @@ -12807,7 +12825,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n# This script automatically sets the SDK and Chat SDK commit hashes (where the application points to) in the AboutTableViewController\ncd ${SRCROOT}/iMEGA/Vendor/SDK\n/usr/libexec/PlistBuddy -c \"Set :SDK_GIT_COMMIT_HASH `git rev-parse --short HEAD`\" \"${TARGET_BUILD_DIR}\"/\"${INFOPLIST_PATH}\"\ncd ${SRCROOT}/iMEGA/Vendor/Karere\n/usr/libexec/PlistBuddy -c \"Set :CHAT_SDK_GIT_COMMIT_HASH `git rev-parse --short HEAD`\" \"${TARGET_BUILD_DIR}\"/\"${INFOPLIST_PATH}\"\n"; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n# This script automatically sets the SDK and Chat SDK commit hashes (where the application points to) in the AboutTableViewController\ncd ${SRCROOT}/Modules/DataSource/MEGASDK/Sources/MEGASDK\n/usr/libexec/PlistBuddy -c \"Set :SDK_GIT_COMMIT_HASH `git rev-parse --short HEAD`\" $SCRIPT_INPUT_FILE_0\ncd ${SRCROOT}/Modules/DataSource/MEGAChatSDK/Sources/MEGAChatSDK\n/usr/libexec/PlistBuddy -c \"Set :CHAT_SDK_GIT_COMMIT_HASH `git rev-parse --short HEAD`\" $SCRIPT_INPUT_FILE_0\n"; }; B67FFF9329D530F500E830FA /* Run Script: Copy GoogleService-Info */ = { isa = PBXShellScriptBuildPhase; @@ -12953,7 +12971,6 @@ 41AC66481AB1D493003FE8DA /* AboutTableViewController.m in Sources */, A8DE32CB22FC14CC006BAC7A /* AccountExpiredViewController.swift in Sources */, 6D64B70D2A09BCB1002ECBD1 /* AccountHallViewModel.swift in Sources */, - B5EF0A8F29F145C600B166B1 /* AccountPlanEntity+Mapper.swift in Sources */, B5EF0A7729F0EE2100B166B1 /* AccountPlanPurchaseRepository.swift in Sources */, B5000FA52A1DB01500E06478 /* AccountPlanView.swift in Sources */, B5000FA62A1DB01500E06478 /* AccountPlanViewModel.swift in Sources */, @@ -13009,6 +13026,7 @@ 32EC280729B9FA2B00D1D18D /* AlbumCoverPickerPhotoSelection.swift in Sources */, 32EC27FD29B987D200D1D18D /* AlbumCoverPickerView.swift in Sources */, 32EC27FF29B987E300D1D18D /* AlbumCoverPickerViewModel.swift in Sources */, + 0E2363B52A9EDDCC0019BBBE /* AlbumEntity+Analytics.swift in Sources */, 32B813AC2A244AA6002A6635 /* AlbumHudMessage.swift in Sources */, 12397E212820C4A600A30CCC /* AlbumListView.swift in Sources */, 12397E232820C4BC00A30CCC /* AlbumListViewModel.swift in Sources */, @@ -13070,6 +13088,7 @@ C49F8621255ABEA80074A054 /* AudioPlayerViewController.swift in Sources */, C49F8631255AC0310074A054 /* AudioPlayerViewModel.swift in Sources */, C49F8629255AC0200074A054 /* AudioPlayerViewRouter.swift in Sources */, + 246ACEB92AA2DAEE000BD2A8 /* AudioPlayerViewRouterNodeActionAdapter.swift in Sources */, C485DA612577F45E0076D362 /* AudioPlaylistIndexedDelegate.swift in Sources */, C480AF4A25711A9200898D48 /* AudioPlaylistIndexedSource.swift in Sources */, C480AF5825711BC100898D48 /* AudioPlaylistSource.swift in Sources */, @@ -13078,7 +13097,7 @@ C44D6DF725701415003D1723 /* AudioPlaylistViewRouter.swift in Sources */, BFC1795024491073004F35AB /* AudioRecorder.swift in Sources */, BF33EF822445401800CD35AC /* AudioRecordingInputBar.swift in Sources */, - BFAC873925FEF26C009CA3F6 /* AudioSessionRepository.swift in Sources */, + 9428430D2AA7CFDD00278048 /* AudioSessionRepository.swift in Sources */, 94312E5029352329005F0F1C /* AudioSessionUseCase+Additions.swift in Sources */, 940DB9142924D844004E7349 /* AudioSessionUseCaseOCWrapper.swift in Sources */, BF017D902448062F00FB4DFD /* AudioWavesView.swift in Sources */, @@ -13103,9 +13122,6 @@ C46527D426526BBA00371270 /* BannerContainerViewController.swift in Sources */, C46527F126526C2300371270 /* BannerContainerViewModel.swift in Sources */, C46527FB26526C3100371270 /* BannerContainerViewRouter.swift in Sources */, - 5D8303E7256270800082CA18 /* BannerEntity.swift in Sources */, - 5D8303F5256367D60082CA18 /* BannerErrorEntity.swift in Sources */, - 5D8303BC256262350082CA18 /* BannerRepository.swift in Sources */, C44947762652A466004B4C8E /* BannerType.swift in Sources */, 5D6092BD256219E1005FA3E4 /* BannerViewModel.swift in Sources */, 2F528CBA2474C0CE006DE5C2 /* BasicAudioController.swift in Sources */, @@ -13136,7 +13152,6 @@ 8330A99328882E12003E80DB /* CallsSettingsViewRouter.swift in Sources */, 83C49BB02652627500A7764B /* CallTitleView.swift in Sources */, 838A7B5625D55EFA00767DB0 /* CallUseCase.swift in Sources */, - 9464C2F528C65CDA00F8B082 /* CameraPositionEntity+Mapper.swift in Sources */, B6D9D7E9216AB0C700D7B08A /* CameraScanner.m in Sources */, B63F3198223BA47400B0C6A6 /* CameraUpload.xcdatamodeld in Sources */, B61B815822D6D9D900B97F51 /* CameraUploadAdvancedOptionsViewController.m in Sources */, @@ -13165,7 +13180,6 @@ 831CDAFC27E0F2DE00A9D367 /* CancellableTransferType.swift in Sources */, 83CBA3E327A81A85005F0FD5 /* CancellableTransferViewModel.swift in Sources */, 83D374E82A431ED400F7B07C /* CancelMeetingAlertDataModel.swift in Sources */, - BFAC87972600041C009CA3F6 /* CaptureDeviceRepository.swift in Sources */, B6E5D251274C5FED00C8AB96 /* CardImage.swift in Sources */, 5D3AD46F252C370C006986AF /* CellConfiguration.swift in Sources */, BF7EBD28244E8B5A0015255F /* CGPoint+Additions.swift in Sources */, @@ -13174,6 +13188,7 @@ 417840A01AC328A600713F3C /* ChangePasswordViewController.m in Sources */, 836C1E132A697E13005874C7 /* ChangeSfuServerRepository.swift in Sources */, 5B39DAA21EF916D900BC2319 /* ChatAttachedContactsViewController.m in Sources */, + 0EE990942AA55D9300D43714 /* ChatAttachedNodesViewController+Additions.swift in Sources */, 5B20F4A21EFA9629007E0A34 /* ChatAttachedNodesViewController.m in Sources */, 2FA4CB71252C019200240AFA /* ChatBottomNewMessageIndicatorView.swift in Sources */, BFE444462914FE1E00AAF882 /* ChatConnectionStatus+Mapper.swift in Sources */, @@ -13322,7 +13337,6 @@ 415DCF241BF48EFA00914A1E /* ContactRequestsViewController.m in Sources */, 83ECE1722488F20E00E9E264 /* ContactsGroupsViewController.swift in Sources */, 83ECE176248900E000E9E264 /* ContactsGroupTableViewCell.swift in Sources */, - B58E375129E808A000188287 /* ContactsPickerViewController.swift in Sources */, 5BD12734248A700800CA95A8 /* ContactsTableViewHeader.swift in Sources */, 8373A16729DB3236005B5951 /* ContactsViewController+Additions.swift in Sources */, 94282D4828A3B3F000F794BA /* ContactsViewController+Backups.swift in Sources */, @@ -13379,8 +13393,6 @@ 0DA5BCB825BA403800530F70 /* DefaultTabTableViewController.swift in Sources */, 9D3563C428B54AF100CB3365 /* DeleteAccountRouter.swift in Sources */, 83DBDDE129D730AE00CF433B /* DetailDisclosureView.swift in Sources */, - 835AD14924A0F438009BC85A /* DeviceContactsManager.swift in Sources */, - 834AFD252498F637005DF37C /* DeviceContactTableViewCell.swift in Sources */, 245CB1AA2A32DE30005F6527 /* DeviceMetaData.swift in Sources */, 24BD7B082A36FD5F0052668B /* DeviceMetaDataFactory.swift in Sources */, 5D31674B24FCA9330032492F /* DevicePermissionAlerting.swift in Sources */, @@ -13451,7 +13463,6 @@ 839A1CA127CCF694004A387B /* ExportFileViewModel.swift in Sources */, B5FE693B286B090900A1725C /* FavouriteExplorerToolbarConfigurator.swift in Sources */, 83BD329825DD3D4000F8B91D /* FavouriteItemsRepository.swift in Sources */, - 830C268125DFC78D00E9B56E /* FavouriteNodesRepository.swift in Sources */, B5FE691328698C5C00A1725C /* FavouritesExplorerGridSource.swift in Sources */, B5FE690F286984AC00A1725C /* FavouritesExplorerGridViewController.swift in Sources */, B57A559628644C5300070BBB /* FavouritesExplorerListSource.swift in Sources */, @@ -13576,6 +13587,7 @@ 5D166B8D252198EF004F4F4A /* HomeSearchHintViewModel.swift in Sources */, 5D166B89252198A7004F4F4A /* HomeSearchResultFileViewModel.swift in Sources */, 5D166B79252196D8004F4F4A /* HomeSearchResultRouter.swift in Sources */, + 94712EF02A9C8E4800B5F2C9 /* HomeSearchResultsProviding.swift in Sources */, 5D038F082511D5AF00DAA2E6 /* HomeSearchResultViewController.swift in Sources */, 5D166B7725219650004F4F4A /* HomeSearchResultViewModel.swift in Sources */, 5D166B7B25219762004F4F4A /* HomeSearchState.swift in Sources */, @@ -13596,7 +13608,6 @@ 5D01995724AD64C1006ED2A0 /* InterfaceStyle.swift in Sources */, 8312D72C22BCE38A00C13BCB /* InviteContactViewController.swift in Sources */, 5B17C9B31F73DE310093F162 /* InviteFriendsViewController.m in Sources */, - BF66DF362612CE3100066F35 /* InviteRequestDelegate.swift in Sources */, 947C6CBA2A5D417200CF929E /* InviteYourFriendsViewController+Additions.swift in Sources */, 835223B1205957DD00A47438 /* ItemCollectionViewCell.m in Sources */, 5D166B8B252198BB004F4F4A /* ItemGroup.swift in Sources */, @@ -13631,7 +13642,7 @@ 4156C80C1AD7E79A00F5E818 /* LTHKeychainUtils.m in Sources */, 4156C80D1AD7E79A00F5E818 /* LTHPasscodeViewController.m in Sources */, 137361901A6664C300B740E8 /* main.m in Sources */, - BF6E8DFF25BA556C003A125A /* MainTabBarController+Additions.swift in Sources */, + 948425DB2A94F34F0024FF1F /* MainTabBarController+Additions.swift in Sources */, C451AB9625A4CDB700A5484B /* MainTabBarController+AudioPlayer.swift in Sources */, B6D995F9222CA42000A98BEF /* MainTabBarController+CameraUpload.m in Sources */, 836A783428E34E5B00B6849F /* MainTabBarController+Chat.swift in Sources */, @@ -13639,7 +13650,6 @@ B55C1B7129890C91006A0205 /* MainTabBarController+SharedItems.swift in Sources */, 943FF9EF292FA33E00B744C8 /* MainTabBarController+SnackBar.swift in Sources */, 415226951A692ECC00EC7BB6 /* MainTabBarController.m in Sources */, - 5BFA9630252E1119003653EB /* ManageChatHistoryRepository.swift in Sources */, B69AF66828A1E5190022B094 /* ManageChatHistoryTableViewController.swift in Sources */, B69AF66B28A1E5190022B094 /* ManageChatHistoryViewModel.swift in Sources */, B69AF66228A1E5190022B094 /* ManageChatHistoryViewRouter.swift in Sources */, @@ -13702,8 +13712,6 @@ 24AB98302A78B84A006E101C /* MEGAAVViewControllerDelegate.swift in Sources */, 243A1AE02A77B8B500ECFC5B /* MEGAAVViewControllerLoadingDecorator.swift in Sources */, B6A4A11421A256B0007E7EEC /* MEGABackgroundTaskOperation.m in Sources */, - 5D83042025638DE70082CA18 /* MEGABanner.swift in Sources */, - 5D83041225638DC40082CA18 /* MEGABannerList.swift in Sources */, 5DAC09D324CFC5B9001B6476 /* MEGABannerView.swift in Sources */, C463CCD526A5E03C00577271 /* MEGAButton.swift in Sources */, A89629751FACE78B00F02F7A /* MEGACallManager.m in Sources */, @@ -13781,6 +13789,7 @@ 77CAAAE0202C58E4004B16ED /* MEGAPhotoBrowserPickerViewController.m in Sources */, BF49DEE72818A5BC00DE3742 /* MEGAPhotoBrowserViewController+Additions.swift in Sources */, B547025228B8AFB6005F7F71 /* MEGAPhotoBrowserViewController+LiveText.swift in Sources */, + 2592C2A22AA00D3100F902C8 /* MEGAPhotoBrowserViewController+SnackBarPresenting.swift in Sources */, 77D9A56C2023547900B48470 /* MEGAPhotoBrowserViewController.m in Sources */, 5DF016812499BD60005A121D /* MEGAPlan.swift in Sources */, 5DF016832499BD7B005A121D /* MEGAPlanService.swift in Sources */, @@ -13790,6 +13799,7 @@ 83850A4526B2BC1100C62304 /* MEGAProviderDelegate+Additions.swift in Sources */, A89629781FACEAF800F02F7A /* MEGAProviderDelegate.m in Sources */, BF8B02F32835A09E009AC794 /* MEGAProviderDelegate.swift in Sources */, + B53B02BE2A96135200550A69 /* MEGAPurchase+PromotedPlan.swift in Sources */, 41D408101B8F240B0001F8BE /* MEGAPurchase.m in Sources */, 83747B1F20ADD16D00BC1AED /* MEGAQLPreviewController.m in Sources */, 5B5E591320EA583900F00A74 /* MEGAQueryRecoveryLinkRequestDelegate.m in Sources */, @@ -13799,7 +13809,6 @@ 2F4291E6257F2CDC00EDF780 /* MEGARecentActionBucket+MNZCategory.m in Sources */, 5B0C55AE1EE9575E0058CBE1 /* MEGARemoveContactRequestDelegate.m in Sources */, 5BFA346B1FAB4169005BFC4E /* MEGARemoveRequestDelegate.m in Sources */, - 5DF0E3D12563A8540052CC95 /* MEGAResultMappingRequestDelegate.swift in Sources */, 5DF0E3D22563A8540052CC95 /* MEGAResultRequestDelegate.swift in Sources */, 5B80B1BB245B2B9700110335 /* MEGASdk+Additions.swift in Sources */, 942F4FD81F500A8F001FC4AC /* MEGASdk+MNZCategory.m in Sources */, @@ -13896,13 +13905,11 @@ C4E78E8C272AF59D00DBF926 /* NodeAccess.swift in Sources */, 0D213EAA263A638C00B43723 /* NodeAccessEntity+Mapper.swift in Sources */, 837B9C07247817F100D409B4 /* NodeActionBuilder.swift in Sources */, - 94658DD729B89B7A0046605A /* NodeActionRepository.swift in Sources */, 943092F42913E26D0092B99A /* NodeActionsRepository.swift in Sources */, 2FB0420E242C4FAE007B9C88 /* NodeActionViewController.swift in Sources */, 5D166B82252197ED004F4F4A /* NodeActionViewControllerGenericDelegate.swift in Sources */, 12E23289286D49000099775D /* NodeActionViewModel.swift in Sources */, 94C85E6C28B93CEF00007A38 /* NodeAssetsManager.swift in Sources */, - A854C21E29AE27AD00CB37E7 /* NodeAttributeRepository.swift in Sources */, 5B947A9A267A20CD00715B0C /* NodeCellViewModel.swift in Sources */, BFEAFF7C2565D57300646606 /* NodeClipboardOperationUseCase.swift in Sources */, B5FC636429E9283C00EE4C8E /* NodeCollectionViewCell+Additions.swift in Sources */, @@ -13912,7 +13919,6 @@ 943092F7291438200092B99A /* NodeDataRepository.swift in Sources */, 5DF44CCC251369E000E9F0BC /* NodeDetailsUseCase.swift in Sources */, 6DE346BF28F7723B00F86E30 /* NodeEntity+NodesUpdate.swift in Sources */, - 5D609288255BA0AE005FA3E4 /* NodeFavouriteActionRepository.swift in Sources */, 6D6FCBF2297E14ED0051EB4A /* NodeFormatEntity+Mapper.swift in Sources */, B616D8342550C16700712012 /* NodeHandle+Additions.swift in Sources */, 833003A624DAFF7F00053B23 /* NodeInfoActionTableViewCell.swift in Sources */, @@ -13945,7 +13951,6 @@ 4152268F1A692ECC00EC7BB6 /* NodeTableViewCell.m in Sources */, 832E1035202DE10500BDD30F /* NodeTappablePropertyTableViewCell.m in Sources */, B678D6D82714266300BBA4E1 /* NodeThumbnailHomeUseCase.swift in Sources */, - 0E32FFB2293E896A004BB11D /* NodeUpdateRepository.swift in Sources */, 943092FA291440010092B99A /* NodeValidationRepository.swift in Sources */, 94B761FC29E5C800003EBC92 /* NodeVersionsViewController+Additions.swift in Sources */, 837B73AF24DD7EAA00321368 /* NodeVersionsViewController.m in Sources */, @@ -14012,8 +14017,10 @@ 2FD6A32A2574A73500C1997E /* PasteImagePreviewView.swift in Sources */, 2FD6A26D25745B2C00C1997E /* PasteImagePreviewViewController.swift in Sources */, 2F26B3282578396E00486C40 /* PasteImagePreviewViewModel.swift in Sources */, + 25D8406C2A9EE2860026D288 /* PermissionAlertModel.swift in Sources */, 9495DBB72A40870C00FA22EE /* PermissionAlertRouter.swift in Sources */, 9422B9F02A41A331007AA5D1 /* PermissionAlertRouting.swift in Sources */, + 25D840692A9EE0E50026D288 /* PermissionAlertViewModifier.swift in Sources */, 9495DBB12A4086BF00FA22EE /* PermissionModalModel.swift in Sources */, B6C9CB0D27FBC2220073BA1A /* PHAsset+CU.swift in Sources */, B630CD502845AAD100A298EE /* PHAssetMediaSubtype+CU.swift in Sources */, @@ -14117,7 +14124,6 @@ 12E14BA2278CB38700AE1BE8 /* PhotoSelection.swift in Sources */, B6F853442794C97300028D51 /* PhotoSelectionAdapter.swift in Sources */, 122B956E287CC7AA00860FE1 /* PhotosFilterOptions.swift in Sources */, - 83C7C85227EDE81F00CFB5E6 /* PhotosLibraryRepository.swift in Sources */, 12397E2C2820C75900A30CCC /* PhotosPageViewController.swift in Sources */, B5CD176427F58236000CF282 /* PhotosViewController+Additions.swift in Sources */, B567EF8A27EAF7640014B26C /* PhotosViewController+Banner.swift in Sources */, @@ -14216,7 +14222,6 @@ 2F2801D924481EFD00C160D0 /* RichPreviewContentView.swift in Sources */, 2F09867A2511BA8900E959C2 /* RichPreviewDialogView.swift in Sources */, C4EDB73826655229006A114F /* RoundedView.swift in Sources */, - 944DB356295EE64900CE4D83 /* RubbishBinRepository.swift in Sources */, B52021782A8BA5F8001B66C7 /* RubbishBinTableViewController+Additions.swift in Sources */, 5B5A983C212EF29500FDBC79 /* RubbishBinTableViewController.m in Sources */, B68BAEC6221DEB4E00FF6FCD /* SavedIdentifierParser.m in Sources */, @@ -14295,6 +14300,7 @@ A811CE2C22EAFB1700B24E76 /* SearchOperation.m in Sources */, 125E606E285A87C2003C7BB5 /* SearchResultErrorEntity.swift in Sources */, 5D4244E0251305BD00EE544D /* SearchResultFileTableViewCell.swift in Sources */, + 947381BA2A9626D000C4E8EC /* SearchResultsBridge.swift in Sources */, 5D1B23F324AF335000ACE1C5 /* SecondaryThemeButtonTextColorFactory.swift in Sources */, 41AA1A1C1AC081C600D32691 /* SecurityOptionsTableViewController.m in Sources */, 9D3563D228B5506A00CB3365 /* SecuritySettingsViewRouter.swift in Sources */, @@ -14411,7 +14417,6 @@ 9D26F234294BADCA00FAD016 /* TextFieldAlertRepresenter.swift in Sources */, 9DD2E84D295558D0000983A3 /* TextFieldAlertViewModel.swift in Sources */, 5D7D53472551263C00D3582E /* TextFieldStyleFactory.swift in Sources */, - 83DBDDE329D730C800CF433B /* TextFieldView.swift in Sources */, 1246682E275DBE0500C94335 /* TextFile.swift in Sources */, 5D64660A24890CC7006030A5 /* TextStyle.swift in Sources */, 5D1B23FC24AF5C8100ACE1C5 /* TextStyleFactory.swift in Sources */, @@ -14499,17 +14504,10 @@ B645165322320C23001680BC /* UploadStats.m in Sources */, B51C55C52824AA04004E3FA8 /* UsageViewController+Additions.swift in Sources */, E889C9E91B566EC400ECEFDF /* UsageViewController.m in Sources */, - 9450A391299E9ABD0031BBEB /* UserAttributeEntity+Mapper.swift in Sources */, - 9450A38F299E62620031BBEB /* UserAttributeRepository.swift in Sources */, 32A2AA062A283BB3007FC630 /* UserAttributeUseCase+Additions.swift in Sources */, 8313914B293519F900E20170 /* UserAvatarView.swift in Sources */, 83139149293511F300E20170 /* UserAvatarViewModel.swift in Sources */, - 5D83042F256392370082CA18 /* UserBannerUseCase.swift in Sources */, - BF782D6D269BA4BB0001D169 /* UserImageLoadErrorEntity.swift in Sources */, - BF90C7802603FD4A006061F8 /* UserImageRepository.swift in Sources */, - BF90C7642603FB42006061F8 /* UserImageRepositoryProtocol.swift in Sources */, BF90C795260406FC006061F8 /* UserImageUseCase.swift in Sources */, - BF5BC7742611687A00716CD6 /* UserInviteRepository.swift in Sources */, BF90C7A726040930006061F8 /* UserStoreRepository.swift in Sources */, B60102C522E172FC00A92C17 /* VerificationCodeViewController.swift in Sources */, B662B86524ED0D800089376A /* VerificationCodeViewModel.swift in Sources */, @@ -14529,6 +14527,7 @@ 9440D6C128BE6D390010D167 /* ViewModePreferenceEntity+Mapper.swift in Sources */, BF006CEB24613BC500C9CADE /* VoiceClipInputBar.swift in Sources */, 99D65D732A8BB5AE005D5877 /* WaitingRoomControlsView.swift in Sources */, + 838838712A97769300148640 /* WaitingRoomEntity+Mapper.swift in Sources */, 99F300982A7A2DDF0087AE7E /* WaitingRoomErrorEntity.swift in Sources */, 992453F02A8DF8FF00ADB76B /* WaitingRoomJoinPanelView.swift in Sources */, 9911BA9C2A8B949200B88F28 /* WaitingRoomMessageView.swift in Sources */, @@ -14656,7 +14655,6 @@ files = ( A8851065292D251200E92A54 /* IntentHandler+StartCall.swift in Sources */, 831B4911256FED6F0086002B /* IntentHandler.swift in Sources */, - A821A5902862257400DB7017 /* IntentPersonProvider.swift in Sources */, 831B497C256FF2580086002B /* ShortcutDetail.swift in Sources */, 83345230258CA2E00052DB3C /* ShortcutIntents.intentdefinition in Sources */, ); @@ -14858,9 +14856,6 @@ BFE54779231F0F29007FB8F6 /* UIViewController+MNZCategory.m in Sources */, B6990181262F92A800176FFC /* UncaughtExceptionHandler.swift in Sources */, 83CCBC47279595D300B90EF2 /* UploadFileRepository.swift in Sources */, - 94A1A2712A83B14E00669C41 /* UserImageLoadErrorEntity.swift in Sources */, - 94A1A2752A83B1B900669C41 /* UserImageRepository.swift in Sources */, - 94A1A2732A83B16000669C41 /* UserImageRepositoryProtocol.swift in Sources */, 94A1A26F2A83B11F00669C41 /* UserImageUseCase.swift in Sources */, 94A1A2782A83B29000669C41 /* UserStoreRepository.swift in Sources */, 940CEE352A83D8C2007DD202 /* WarningView.swift in Sources */, @@ -14869,6 +14864,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 945222F92A98AB1700C9DFAF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 945223022A98AB1700C9DFAF /* ContentView.swift in Sources */, + 945223002A98AB1700C9DFAF /* SearchDemoApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; A8399F3E296C40060062B6BC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -14880,8 +14884,6 @@ A8C47595299D03EE0074511F /* MEGAConstants.m in Sources */, A899467029716B4A008448C4 /* MEGAFunctions.swift in Sources */, A8DEF2BA298BB40F00137E38 /* MEGASdk+SharedInstanceWrapper.swift in Sources */, - A8C4754E299A78DD0074511F /* NodeActionRepository.swift in Sources */, - A854C21D29AE1FB800CB37E7 /* NodeAttributeRepository.swift in Sources */, A8B09DE929B0CBD600C922B9 /* PickerConstant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -14930,6 +14932,7 @@ C452B66425CD50260073B22C /* AudioPlayerPlaybackTests.swift in Sources */, 24A6ADDC2A8222CE0035C7B0 /* AudioPlayerPlaylistShiftStrategyTestSpecs.swift in Sources */, C45A2E1925D2E00400915A83 /* AudioPlayerViewModelTests.swift in Sources */, + 246ACEB52AA2D9C5000BD2A8 /* AudioPlayerViewRouterNodeActionAdapterTests.swift in Sources */, 24F231592A5FB33A000BFBBA /* AudioPlayerViewRouterTests.swift in Sources */, C45A2DF525D2B97F00915A83 /* AudioPlaylistViewModelTests.swift in Sources */, 2546EE052A4149B300CBB82E /* AVPlayerManagerTests.swift in Sources */, @@ -14958,6 +14961,7 @@ 2FF3FA6B260C39A300015050 /* DeepLinkingTests.m in Sources */, B6AF22DF26097F500008FF72 /* DiskFullBlockingViewModelTests.swift in Sources */, 2435921E2A4ABBC100B0C57E /* DocScannerSaveSettingTableViewControllerTests.swift in Sources */, + 25ED56812A9D867D0063864F /* DownloadFileRepositoryTests.swift in Sources */, 837B17A328819CF600A4D01B /* DownloadLinkViewModelTests.swift in Sources */, 274514C22A2F5BDE00BFFB2F /* ElementStoreTests.swift in Sources */, BF2B2C82263F549D006E12A4 /* EndMeetingOptionsViewModelTests.swift in Sources */, @@ -14981,6 +14985,7 @@ 0E3B1A4A2A1DBA5500EC5859 /* GetLinkSwitchOptionCellViewModelTests.swift in Sources */, BF66CA7728B83ACF00DB686E /* HangOrEndCallViewModelTests.swift in Sources */, 8340CB1C284F5507007C3B39 /* HeaderViewModelTests.swift in Sources */, + 94712EF42A9C8F9400B5F2C9 /* HomeSearchResultsProvidingTests.swift in Sources */, 328C153B2A3989B1002499CE /* ImportAlbumViewModelTests.swift in Sources */, 1240A4A327DD548600627262 /* MediaDiscoveryViewModelTests.swift in Sources */, BFE3B9F2285838A7003949FF /* MeetingContainerViewModel+Additions.swift in Sources */, @@ -15026,6 +15031,7 @@ 2FE5438D264E2BF400FF4955 /* MockMeetingCreatingUseCase.swift in Sources */, B5663EE029F8F3DB0080FFDA /* MockMEGAPurchase.swift in Sources */, C45A2E2D25D2E09C00915A83 /* MockMiniPlayerViewRouter.swift in Sources */, + 94712EFA2A9C9F5700B5F2C9 /* MockNodeDetailsUseCase.swift in Sources */, C47E7C0225D18FDF00DB6FF5 /* MockNodeInfoRepository.swift in Sources */, BFE3B9DE2856A8A1003949FF /* MockNoUserJoinedUseCase.swift in Sources */, C47E7C1C25D1A84200DB6FF5 /* MockOfflineFileInfoRepository.swift in Sources */, @@ -15034,18 +15040,18 @@ 8394EB972643E5200064F741 /* MockRemoteVideoUseCase.swift in Sources */, 83669E8C2987BF6B003E8F74 /* MockScheduledMeetingUseCase.swift in Sources */, BFCA570F2A5525DD0033BEE2 /* MockScheduleMeetingViewConfiguration.swift in Sources */, + 94712EFD2A9C9FAD00B5F2C9 /* MockSearchFileUseCase.swift in Sources */, B5663EE229F8F7FC0080FFDA /* MockSKProduct.swift in Sources */, 32D167AA2942DA59008C5ED6 /* MockSlideShowDataSource.swift in Sources */, C47E7C2E25D1A8C800DB6FF5 /* MockStreaminginfoRepository.swift in Sources */, + 25ED566A2A98699E0063864F /* MockTransferWidgetResponder.swift in Sources */, 0D0B54622627A92D00E9BB16 /* MockUploadFileUseCase.swift in Sources */, BF8895CA2630F4B3002A18CA /* MockUserImageUseCase.swift in Sources */, - BFDE0EEB263A44BC00FC586E /* MockUserInviteUseCase.swift in Sources */, 8361D9F2283E7B4E0001FAC1 /* NameCollisionViewModelTests.swift in Sources */, 94CC921829F0106000FA149A /* NodeAccessEntity+Mapper_Tests.swift in Sources */, 837B9C0B24781B1600D409B4 /* NodeActionBuilderTests.swift in Sources */, 9487BF3D29B6744000645D32 /* NodeActionRepositoryTests.swift in Sources */, 2488EBC32A13B503005F910E /* NodeActionViewControllerGenericDelegateTests.swift in Sources */, - A8919424299F90A700B2A225 /* NodeAttributeRepositoryTests.swift in Sources */, 5BBC13E626CE64F50010115B /* NodeCellViewModelTests.swift in Sources */, B5FC636929E9351100EE4C8E /* NodeCollectionViewCellViewModelTests.swift in Sources */, 0E45ACB429BEB72600541278 /* NodeEntityMapppingTests.swift in Sources */, @@ -15055,13 +15061,13 @@ C45A2E3825D2F80900915A83 /* NodeInfoUseCaseTests.swift in Sources */, 245385FA2A13C05B00208B31 /* NodeInfoViewControllerTests.swift in Sources */, 94E37E5229D1A9B0009E396D /* NodeRepositoryTests.swift in Sources */, - 0E32FFAA293E817A004BB11D /* NodeUpdateRepositoryTests.swift in Sources */, 9449938F28BD312D001AE14B /* NodeValidationRepositoryTests.swift in Sources */, 2F01A4332612E46800C1D752 /* NSURL+Deeplinking.swift in Sources */, C45A2E4A25D2F81800915A83 /* OfflineFileInfoUseCaseTests.swift in Sources */, 160711542A558DED00970A19 /* PagerTabViewModelTests.swift in Sources */, 12B1E58328751FD100D23A58 /* PageStayTimeTrackerTests.swift in Sources */, BFC060F628A0C2FE00BA2AD7 /* participantsAddingViewFactoryTests.swift in Sources */, + 25D840712A9EE9330026D288 /* PermissionAlertModelTests.swift in Sources */, 946544862A4446D50038D8FC /* PermissionAlertRouterTests.swift in Sources */, 160711652A5594AD00970A19 /* PhotoAlbumContainerViewModelTests.swift in Sources */, B64A71CB287B8C000085087D /* PhotoBrowserDataProvider_megaNode_Tests.swift in Sources */, @@ -15090,11 +15096,11 @@ BF6E8D2D25B636FB003A125A /* PSAViewModelTests.swift in Sources */, 247FC1BE2A4F1C3700CC8518 /* QASettingsRouterTests.swift in Sources */, 24A619DF2A52735600EBB863 /* QASettingsViewModelTests.swift in Sources */, + 948176C52A93E9DC00E3A181 /* QuickAccessWidgetManagerTests.swift in Sources */, B6AADE952518456B009A874C /* RegionListViewModelTests.swift in Sources */, 240380542A30687500C06E61 /* ReportIssueMessageViewModelTests.swift in Sources */, A8EE47AA27DFAEF000D37685 /* ReportIssueViewModelTests.swift in Sources */, B6AADE9C251845E2009A874C /* Routing+Addition.swift in Sources */, - 94417EAF296D825B007C9230 /* RubbishBinRepositoryTests.swift in Sources */, BF418B7E2A43DE14004C5A5D /* ScheduledMeetingDateBuilderTests.swift in Sources */, 83522FCA2A4B0A2600266D1F /* ScheduledMeetingOccurrencesViewModelTests.swift in Sources */, BFB8897B2A13600400FC456A /* ScheduledMeetingRulesEntityTests.swift in Sources */, @@ -15137,7 +15143,6 @@ B6AADE8E25183626009A874C /* VerificationCodeViewModelTests.swift in Sources */, 9961E7262A283030004FB05F /* VideoExplorerTableCellViewModelTests.swift in Sources */, BFB889932A1598DB00FC456A /* WaitingRoomViewModel+Additions.swift in Sources */, - BFB889932A1598DB00FC456A /* WaitingRoomViewModel+Additions.swift in Sources */, 99487BCC2A8F328D00BE4685 /* WaitingRoomViewModelTests.swift in Sources */, B5B0E30627F1BCA700DB6146 /* WarningViewModelTests.swift in Sources */, B60F05E92514C022005133C2 /* XCTestCase+ActionCommand.swift in Sources */, @@ -15217,33 +15222,6 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 415227411A692F8D00EC7BB6 /* Localizable.strings */ = { - isa = PBXVariantGroup; - children = ( - E8860CCF1C7724A00026ABBC /* ar */, - 415227401A692F8D00EC7BB6 /* Base */, - E84F86FF1BB99E6A00436A0D /* de */, - 410333D71B9993A000380FF3 /* en */, - 415227421A692F9B00EC7BB6 /* es */, - E84F86FD1BB99DE200436A0D /* fr */, - E84F87041BB9A02800436A0D /* id */, - 410333E11B99948600380FF3 /* it */, - E84F86FE1BB99E1700436A0D /* ja */, - E84F87011BB99F5F00436A0D /* ko */, - 410333EB1B9994F400380FF3 /* nl */, - E84F87021BB99FA500436A0D /* pl */, - A81BD63327EDC4C100234902 /* pt */, - E84F87051BB9A07200436A0D /* ro */, - 410333EF1B99951900380FF3 /* ru */, - E894271F1CCF8411008D470D /* th */, - E84F87091BB9A27600436A0D /* vi */, - E8B64EFB1BC6E48C005C726E /* zh-Hans */, - 410333FA1B9995DC00380FF3 /* zh-Hant */, - ); - name = Localizable.strings; - path = ../Languages; - sourceTree = ""; - }; 83345232258CA2E00052DB3C /* ShortcutIntents.intentdefinition */ = { isa = PBXVariantGroup; children = ( @@ -15304,32 +15282,6 @@ name = AppIntentVocabulary.plist; sourceTree = ""; }; - B5A99634282CF625008B8ADD /* Localizable.stringsdict */ = { - isa = PBXVariantGroup; - children = ( - B5A9963F282CF631008B8ADD /* ar */, - B5A99635282CF625008B8ADD /* Base */, - B5A99644282CF63D008B8ADD /* de */, - B5A9963E282CF62F008B8ADD /* en */, - B5A9964D282CF64C008B8ADD /* es */, - B5A99643282CF63C008B8ADD /* fr */, - B5A99645282CF63F008B8ADD /* id */, - B5A99646282CF640008B8ADD /* it */, - B5A99647282CF642008B8ADD /* ja */, - B5A99648282CF643008B8ADD /* ko */, - B5A99642282CF63A008B8ADD /* nl */, - B5A99649282CF645008B8ADD /* pl */, - B5A9964A282CF646008B8ADD /* pt */, - B5A9964B282CF649008B8ADD /* ro */, - B5A9964C282CF64A008B8ADD /* ru */, - B5A9964E282CF64D008B8ADD /* th */, - B5A9964F282CF64F008B8ADD /* vi */, - B5A99640282CF633008B8ADD /* zh-Hans */, - B5A99641282CF637008B8ADD /* zh-Hant */, - ); - name = Localizable.stringsdict; - sourceTree = ""; - }; E803CB661D5336F100F81A36 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -15412,7 +15364,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 11.1.1; + MARKETING_VERSION = 11.2.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ""; @@ -15422,6 +15374,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_REFLECTION_METADATA_LEVEL = all; SWIFT_STRICT_CONCURRENCY = targeted; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -15472,7 +15425,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 11.1.1; + MARKETING_VERSION = 11.2.1; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "-DNDEBUG"; OTHER_LDFLAGS = "-ObjC"; @@ -15480,6 +15433,7 @@ SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_STRICT_CONCURRENCY = targeted; + SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -15535,6 +15489,7 @@ PROVISIONING_PROFILE_SPECIFIER = "match Development mega.ios.dev"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "iMEGA/Supporting Files/MEGA-Bridging-Header.h"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_VERSION = 5.0; USER_HEADER_SEARCH_PATHS = ( "Modules/DataSource/MEGASDK/Sources/MEGASDK/bindings/ios/3rdparty/webrtc/sdk/objc/**", @@ -15591,6 +15546,7 @@ PROVISIONING_PROFILE_SPECIFIER = "match AppStore mega.ios"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "iMEGA/Supporting Files/MEGA-Bridging-Header.h"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; SWIFT_VERSION = 5.0; USER_HEADER_SEARCH_PATHS = ( "Modules/DataSource/MEGASDK/Sources/MEGASDK/bindings/ios/3rdparty/webrtc/sdk/objc/**", @@ -15995,6 +15951,125 @@ }; name = Release; }; + 945223082A98AB1900C9DFAF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + ENABLE_PREVIEWS = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mega.SearchDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 945223092A98AB1900C9DFAF /* QA */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_ASSET_PATHS = ""; + ENABLE_PREVIEWS = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mega.SearchDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = QA; + }; + 9452230A2A98AB1900C9DFAF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_ASSET_PATHS = ""; + ENABLE_PREVIEWS = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mega.SearchDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; A8399F52296C40090062B6BC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -16396,7 +16471,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 11.1.1; + MARKETING_VERSION = 11.2.1; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "-DNDEBUG"; OTHER_LDFLAGS = "-ObjC"; @@ -16404,6 +16479,7 @@ SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_STRICT_CONCURRENCY = targeted; + SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -16457,6 +16533,7 @@ PROVISIONING_PROFILE_SPECIFIER = "match AdHoc mega.ios.qa"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "iMEGA/Supporting Files/MEGA-Bridging-Header.h"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; SWIFT_VERSION = 5.0; USER_HEADER_SEARCH_PATHS = ( "Modules/DataSource/MEGASDK/Sources/MEGASDK/bindings/ios/3rdparty/webrtc/sdk/objc/**", @@ -16776,6 +16853,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 9452230B2A98AB1900C9DFAF /* Build configuration list for PBXNativeTarget "SearchDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 945223082A98AB1900C9DFAF /* Debug */, + 945223092A98AB1900C9DFAF /* QA */, + 9452230A2A98AB1900C9DFAF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; A8399F51296C40090062B6BC /* Build configuration list for PBXNativeTarget "MEGAPickerFileProvider" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -16988,10 +17075,6 @@ isa = XCSwiftPackageProductDependency; productName = MEGATest; }; - 3241349629FB712B006E5310 /* MEGASdk */ = { - isa = XCSwiftPackageProductDependency; - productName = MEGASdk; - }; 327717B429FB82C4004DE295 /* MEGAPresentation */ = { isa = XCSwiftPackageProductDependency; productName = MEGAPresentation; @@ -17004,6 +17087,10 @@ isa = XCSwiftPackageProductDependency; productName = Settings; }; + 940F41372A9FC40B00C2543F /* MEGAIntentDomain */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAIntentDomain; + }; 9418DB282A4EE5FB002B0077 /* MEGAPermissions */ = { isa = XCSwiftPackageProductDependency; productName = MEGAPermissions; @@ -17012,6 +17099,10 @@ isa = XCSwiftPackageProductDependency; productName = MEGAPermissionsMock; }; + 9452230D2A98AB6300C9DFAF /* Search */ = { + isa = XCSwiftPackageProductDependency; + productName = Search; + }; 946336D92A8ABA23005C58D9 /* SwiftGen */ = { isa = XCSwiftPackageProductDependency; productName = "plugin:SwiftGen"; @@ -17052,6 +17143,10 @@ isa = XCSwiftPackageProductDependency; productName = MEGAPresentation; }; + 948425E22A9504400024FF1F /* Search */ = { + isa = XCSwiftPackageProductDependency; + productName = Search; + }; 94A220172A696502004130C4 /* MEGAAnalyticsiOS */ = { isa = XCSwiftPackageProductDependency; package = 943E6BBC2A60056100A92F94 /* XCRemoteSwiftPackageReference "mobile-analytics-ios" */; @@ -17069,9 +17164,9 @@ isa = XCSwiftPackageProductDependency; productName = "plugin:SwiftGen"; }; - A803C8FA2955DB550056C9F0 /* MEGADomain */ = { + 94FD8A8A2AA0C6F300CD1C8B /* MEGARepo */ = { isa = XCSwiftPackageProductDependency; - productName = MEGADomain; + productName = MEGARepo; }; A803C8FC2955DB790056C9F0 /* SAMKeychain */ = { isa = XCSwiftPackageProductDependency; @@ -17358,6 +17453,66 @@ package = BF1CEA3227FA737200C1A2E5 /* XCRemoteSwiftPackageReference "GKContactImage" */; productName = GKContactImage; }; + BF29179C2A9D8DD800A59D12 /* ChatRepo */ = { + isa = XCSwiftPackageProductDependency; + productName = ChatRepo; + }; + BF6149512A9FF33300AB051A /* MEGAL10n */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10n; + }; + BF6149532A9FF33300AB051A /* MEGAL10nObjc */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10nObjc; + }; + BF6149552A9FF34400AB051A /* MEGAL10n */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10n; + }; + BF6149572A9FF34400AB051A /* MEGAL10nObjc */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10nObjc; + }; + BF6149592A9FF35200AB051A /* MEGAL10n */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10n; + }; + BF61495B2A9FF35200AB051A /* MEGAL10nObjc */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10nObjc; + }; + BF61495D2A9FF36200AB051A /* MEGAL10n */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10n; + }; + BF61495F2A9FF36200AB051A /* MEGAL10nObjc */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10nObjc; + }; + BF6149612A9FF37000AB051A /* MEGAL10n */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10n; + }; + BF6149632A9FF37000AB051A /* MEGAL10nObjc */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10nObjc; + }; + BF6149652A9FF38400AB051A /* MEGAL10n */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10n; + }; + BF6149672A9FF38400AB051A /* MEGAL10nObjc */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10nObjc; + }; + BF6149692A9FF39500AB051A /* MEGAL10n */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10n; + }; + BF61496B2A9FF39500AB051A /* MEGAL10nObjc */ = { + isa = XCSwiftPackageProductDependency; + productName = MEGAL10nObjc; + }; BFA275E227ED315E00E15B92 /* SDAVAssetExportSession */ = { isa = XCSwiftPackageProductDependency; package = BFA275E127ED313500E15B92 /* XCRemoteSwiftPackageReference "SDAVAssetExportSession" */; diff --git a/MEGA.xcodeproj/xcshareddata/xcschemes/MEGA.xcscheme b/MEGA.xcodeproj/xcshareddata/xcschemes/MEGA.xcscheme index 1f2a98a713..0eb67e2bce 100644 --- a/MEGA.xcodeproj/xcshareddata/xcschemes/MEGA.xcscheme +++ b/MEGA.xcodeproj/xcshareddata/xcschemes/MEGA.xcscheme @@ -20,6 +20,20 @@ ReferencedContainer = "container:MEGA.xcodeproj"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MEGAData/Repository/Account/AccountRepository.swift b/MEGAData/Repository/Account/AccountRepository.swift index e843a2fe64..f1f87ceab6 100644 --- a/MEGAData/Repository/Account/AccountRepository.swift +++ b/MEGAData/Repository/Account/AccountRepository.swift @@ -13,8 +13,8 @@ final class AccountRepository: NSObject, AccountRepositoryProtocol { private let sdk: MEGASdk private let currentUserSource: CurrentUserSource - private let requestResultSourcePublisher = PassthroughSubject, Never>() - var requestResultPublisher: AnyPublisher, Never> { + private let requestResultSourcePublisher = PassthroughSubject, Never>() + var requestResultPublisher: AnyPublisher, Never> { requestResultSourcePublisher.eraseToAnyPublisher() } @@ -78,7 +78,7 @@ final class AccountRepository: NSObject, AccountRepositoryProtocol { } func incomingContactsRequestsCount() -> Int { - sdk.incomingContactRequests().size.intValue + sdk.incomingContactRequests().size } func relevantUnseenUserAlertsCount() -> UInt { diff --git a/MEGAData/Repository/AudioVideo/AudioSessionRepository.swift b/MEGAData/Repository/AudioVideo/AudioSessionRepository.swift index eaf8ac33da..a70273585c 100644 --- a/MEGAData/Repository/AudioVideo/AudioSessionRepository.swift +++ b/MEGAData/Repository/AudioVideo/AudioSessionRepository.swift @@ -7,7 +7,7 @@ final class AudioSessionRepository: AudioSessionRepositoryProtocol { var routeChanged: ((_ reason: AudioSessionRouteChangedReason, _ previousAudioPort: AudioPort?) -> Void)? var isBluetoothAudioRouteAvailable: Bool { - audioSession.mnz_isBluetoothAudioRouteAvailable + audioSession.isBluetoothAudioRouteAvailable } var currentSelectedAudioPort: AudioPort { @@ -137,7 +137,7 @@ final class AudioSessionRepository: AudioSessionRepositoryProtocol { return false } - return audioSession.mnz_isOutputEqual(toPortType: avAudioSessionPort) + return audioSession.isOutputEqualToPortType(avAudioSessionPort) } // MARK: - Private methods diff --git a/MEGAData/Repository/Calls/CallRepository.swift b/MEGAData/Repository/Calls/CallRepository.swift index dce7e55126..54fd94d019 100644 --- a/MEGAData/Repository/Calls/CallRepository.swift +++ b/MEGAData/Repository/Calls/CallRepository.swift @@ -3,6 +3,10 @@ import MEGADomain final class CallRepository: NSObject, CallRepositoryProtocol { + static var newRepo: CallRepository { + CallRepository(chatSdk: .shared, callActionManager: .shared) + } + private let chatSdk: MEGAChatSdk private let callActionManager: CallActionManager private var callbacksDelegate: (any CallCallbacksRepositoryProtocol)? @@ -105,6 +109,25 @@ final class CallRepository: NSObject, CallRepositoryProtocol { } } + func startMeetingInWaitingRoomChat(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) { + let delegate = createStartMeetingRequestDelegate(for: scheduledMeeting.chatId, completion: completion) + callActionManager.startMeetingInWaitingRoomChat(chatId: scheduledMeeting.chatId, scheduledId: scheduledMeeting.scheduledId, enableVideo: enableVideo, enableAudio: enableAudio, delegate: delegate) + } + + @MainActor + func startMeetingInWaitingRoomChat(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool) async throws -> CallEntity { + try await withCheckedThrowingContinuation { continuation in + startMeetingInWaitingRoomChat(for: scheduledMeeting, enableVideo: enableVideo, enableAudio: enableAudio) { result in + switch result { + case .success(let callEntity): + continuation.resume(returning: callEntity) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + func joinCall(for chatId: HandleEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) { guard let activeCall = chatSdk.chatCall(forChatId: chatId) else { completion(.failure(.generic)) @@ -144,6 +167,18 @@ final class CallRepository: NSObject, CallRepositoryProtocol { chatSdk.remove(fromChat: call.chatId, userHandle: peerId) } + func allowUsersJoinCall(_ call: CallEntity, users: [UInt64]) { + chatSdk.allowUsersJoinCall(call.chatId, usersHandles: users.map(NSNumber.init(value:))) + } + + func kickUsersFromCall(_ call: CallEntity, users: [UInt64]) { + chatSdk.kickUsers(fromCall: call.callId, usersHandles: users.map(NSNumber.init(value:))) + } + + func pushUsersIntoWaitingRoom(for scheduledMeeting: ScheduledMeetingEntity, users: [UInt64]) { + chatSdk.pushUsers(intoWaitingRoom: scheduledMeeting.chatId, usersHandles: users.map(NSNumber.init(value:))) + } + func makePeerAModerator(inCall call: CallEntity, peerId: UInt64) { chatSdk.updateChatPermissions(call.chatId, userHandle: peerId, privilege: MEGAChatRoomPrivilege.moderator.rawValue) } @@ -317,6 +352,16 @@ extension CallRepository: MEGAChatCallDelegate { break } } + + if call.hasChanged(for: .waitingRoomUsersEntered) { + guard let usersHandle = call.waitingRoomHandleList.toHandleEntityArray() else { return } + callbacksDelegate?.waitingRoomUsersEntered(with: usersHandle) + } + + if call.hasChanged(for: .waitingRoomUsersLeave) { + guard let usersHandle = call.waitingRoomHandleList.toHandleEntityArray() else { return } + callbacksDelegate?.waitingRoomUsersLeave(with: usersHandle) + } case .terminatingUserParticipation, .destroyed: callbacksDelegate?.callTerminated(call.toCallEntity()) case .userNoPresent: diff --git a/MEGAData/Repository/Calls/MeetingCreatingRepository.swift b/MEGAData/Repository/Calls/MeetingCreatingRepository.swift index d3e25fcaa6..eccb810b26 100644 --- a/MEGAData/Repository/Calls/MeetingCreatingRepository.swift +++ b/MEGAData/Repository/Calls/MeetingCreatingRepository.swift @@ -1,3 +1,4 @@ +import ChatRepo import Foundation import MEGADomain import MEGASDKRepo @@ -36,26 +37,31 @@ final class MeetingCreatingRepository: NSObject, MEGAChatDelegate, MeetingCreati } func startCall(_ startCall: StartCallEntity, completion: @escaping (Result) -> Void) { - let delegate = MEGAChatGenericRequestDelegate { [weak self] (request, _) in - guard let self = self else { return } - guard let chatroom = self.chatSdk.chatRoom(forChatId: request.chatHandle) else { - MEGALogDebug("ChatRoom not found with chat handle \(MEGASdk.base64Handle(forUserHandle: request.chatHandle) ?? "-1")") - return - } - - let startCallDelegate = MEGAChatStartCallRequestDelegate { [weak self] (chatError) in - if chatError.type == .MEGAChatErrorTypeOk { - guard (self?.chatSdk.chatCall(forChatId: request.chatHandle)) != nil else { + let delegate = ChatRequestDelegate { [weak self] result in + switch result { + case .success(let request): + guard let self, let chatroom = chatSdk.chatRoom(forChatId: request.chatHandle) else { + MEGALogDebug("ChatRoom not found with chat handle \(MEGASdk.base64Handle(forUserHandle: request.chatHandle) ?? "-1")") + completion(.failure(.generic)) + return + } + + let startCallDelegate = MEGAChatStartCallRequestDelegate { [weak self] (chatError) in + if chatError.type == .MEGAChatErrorTypeOk { + guard (self?.chatSdk.chatCall(forChatId: request.chatHandle)) != nil else { + completion(.failure(.generic)) + return + } + completion(.success(chatroom.toChatRoomEntity())) + } else { completion(.failure(.generic)) - return } - completion(.success(chatroom.toChatRoomEntity())) - } else { - completion(.failure(.generic)) } + + callActionManager.startCall(chatId: chatroom.chatId, enableVideo: startCall.enableVideo, enableAudio: startCall.enableAudio, delegate: startCallDelegate) + case .failure: + completion(.failure(.generic)) } - - self.callActionManager.startCall(chatId: chatroom.chatId, enableVideo: startCall.enableVideo, enableAudio: startCall.enableAudio, delegate: startCallDelegate) } chatSdk.createMeeting( @@ -68,19 +74,24 @@ final class MeetingCreatingRepository: NSObject, MEGAChatDelegate, MeetingCreati } func joinChatCall(forChatId chatId: UInt64, enableVideo: Bool, enableAudio: Bool, userHandle: UInt64, completion: @escaping (Result) -> Void) { - let delegate = MEGAChatGenericRequestDelegate { [weak self] (request, _) in - guard let self = self, let megaChatRoom = self.chatSdk.chatRoom(forChatId: request.chatHandle) else { - MEGALogDebug("ChatRoom not found with chat handle \(MEGASdk.base64Handle(forUserHandle: request.chatHandle) ?? "-1")") + let delegate = ChatRequestDelegate { [weak self] result in + switch result { + case .success(let request): + guard let self, let megaChatRoom = chatSdk.chatRoom(forChatId: request.chatHandle) else { + MEGALogDebug("ChatRoom not found with chat handle \(MEGASdk.base64Handle(forUserHandle: request.chatHandle) ?? "-1")") + completion(.failure(.generic)) + return + } + + let chatRoom = megaChatRoom.toChatRoomEntity() + MEGALogDebug("Create meeting: Answer call with chatroom id \(MEGASdk.base64Handle(forUserHandle: chatRoom.chatId) ?? "-1")") + answerCall(for: chatRoom, enableVideo: enableVideo, enableAudio: enableAudio, completion: completion) + case .failure: completion(.failure(.generic)) - return } - - let chatRoom = megaChatRoom.toChatRoomEntity() - MEGALogDebug("Create meeting: Answer call with chatroom id \(MEGASdk.base64Handle(forUserHandle: chatRoom.chatId) ?? "-1")") - self.answerCall(for: chatRoom, enableVideo: enableVideo, enableAudio: enableAudio, completion: completion) } - if let megaChatRoom = self.chatSdk.chatRoom(forChatId: chatId), + if let megaChatRoom = chatSdk.chatRoom(forChatId: chatId), !megaChatRoom.isPreview, !megaChatRoom.isActive { chatSdk.autorejoinPublicChat(chatId, publicHandle: userHandle, delegate: delegate) @@ -88,9 +99,34 @@ final class MeetingCreatingRepository: NSObject, MEGAChatDelegate, MeetingCreati MEGALogDebug("Create meeting: Autojoin public chat with chatId - \(MEGASdk.base64Handle(forUserHandle: chatId) ?? "-1")") chatSdk.autojoinPublicChat(chatId, delegate: delegate) } - } + func joinChatWithoutAnswerCall(forChatId chatId: UInt64, userHandle: UInt64, completion: @escaping (Result) -> Void) { + let delegate = ChatRequestDelegate { [weak self] result in + switch result { + case .success(let request): + guard let self, let megaChatRoom = chatSdk.chatRoom(forChatId: request.chatHandle) else { + MEGALogDebug("ChatRoom not found with chat handle \(MEGASdk.base64Handle(forUserHandle: request.chatHandle) ?? "-1")") + completion(.failure(.generic)) + return + } + + let chatRoom = megaChatRoom.toChatRoomEntity() + completion(.success(chatRoom)) + case .failure: + completion(.failure(.generic)) + } + } + + if let megaChatRoom = chatSdk.chatRoom(forChatId: chatId), + !megaChatRoom.isPreview, + !megaChatRoom.isActive { + chatSdk.autorejoinPublicChat(chatId, publicHandle: userHandle, delegate: delegate) + } else { + chatSdk.autojoinPublicChat(chatId, delegate: delegate) + } + } + func checkChatLink(link: String, completion: @escaping (Result) -> Void) { guard let url = URL(string: link) else { completion(.failure(.generic)) @@ -98,54 +134,68 @@ final class MeetingCreatingRepository: NSObject, MEGAChatDelegate, MeetingCreati } MEGALogDebug("Create meeting: check chat link \(link)") - chatSdk.checkChatLink(url, delegate: MEGAChatGenericRequestDelegate(completion: { [weak self] (request, error) in - - guard error.type == .MEGAChatErrorTypeOk || error.type == .MegaChatErrorTypeExist else { - MEGALogDebug("Create meeting: failed to check chat link \(link)") + chatSdk.checkChatLink(url, delegate: ChatRequestDelegate { [weak self] result in + guard let self else { completion(.failure(.generic)) return } - - guard let chatroom = self?.chatSdk.chatRoom(forChatId: request.chatHandle) else { - MEGALogDebug("Create meeting: ChatRoom not found with chat handle \(request.chatHandle)") + switch result { + case .success(let request): + + guard let chatroom = chatSdk.chatRoom(forChatId: request.chatHandle) else { + MEGALogDebug("Create meeting: ChatRoom not found with chat handle \(request.chatHandle)") + completion(.failure(.generic)) + return + } + + MEGALogDebug("Create meeting: check chat link succeeded with chatroom \(chatroom)") + completion(.success(chatroom.toChatRoomEntity())) + case .failure(let error): + if error.type == .MegaChatErrorTypeExist { + MEGALogDebug("Create meeting: failed to check chat link \(link)") + } completion(.failure(.generic)) - return } - - MEGALogDebug("Create meeting: check chat link succeded with chatroom \(chatroom)") - completion(.success(chatroom.toChatRoomEntity())) - })) + }) } func createEphemeralAccountAndJoinChat(firstName: String, lastName: String, link: String, completion: @escaping (Result) -> Void, karereInitCompletion: @escaping () -> Void) { MEGALogDebug("Create meeting: Now logging out of anonymous account") - chatSdk.logout(with: MEGAChatResultRequestDelegate(completion: { (result) in + chatSdk.logout(with: ChatRequestDelegate { [weak self] result in + guard let self else { + completion(.failure(.unexpected)) + return + } switch result { case .success: - self.chatSdk.initKarere(withSid: nil) + chatSdk.initKarere(withSid: nil) karereInitCompletion() MEGALogDebug("Create meeting: Now creating ephemeral account plus plus with firstname - \(firstName) and lastname - \(lastName)") - self.sdk.createEphemeralAccountPlusPlus(withFirstname: firstName, lastname: lastName, delegate: MEGAResultRequestDelegate { (result) in + sdk.createEphemeralAccountPlusPlus(withFirstname: firstName, lastname: lastName, delegate: RequestDelegate { [weak self] result in + guard let self else { + completion(.failure(.unexpected)) + return + } switch result { case .failure(let errorType): MEGALogDebug("Create meeting: failed creating ephemeral account plus plus with error \(errorType)") - completion(.failure(errorType)) + completion(.failure(.unexpected)) case .success(let request): MEGALogDebug("Create meeting: success creating ephemeral account plus plus") if request.paramType == AccountActionType.resumeEphemeralPlusPlus.rawValue { MEGALogDebug("Create meeting: Now fetching node for ephemeral account") - self.sdk.fetchNodes(with: RequestDelegate(completion: { (result) in + sdk.fetchNodes(with: RequestDelegate { [weak self] result in switch result { case .success: MEGALogDebug("Create meeting: success fetching node for ephemeral account and now connecting to chat") - self.connectToChat(link: link, request: request, completion: completion) + self?.connectToChat(link: link, request: request, completion: completion) case .failure(let error): MEGALogDebug("Create meeting: failure fetching node for ephemeral account \(error)") completion(.failure(.unexpected)) } - })) + }) } else { - self.connectToChat(link: link, request: request, completion: completion) + connectToChat(link: link, request: request, completion: completion) } } }) @@ -153,8 +203,7 @@ final class MeetingCreatingRepository: NSObject, MEGAChatDelegate, MeetingCreati MEGALogDebug("Create meeting: failed to logout of anonymous account \(error)") completion(.failure(.unexpected)) } - - })) + }) } private func connectToChat(link: String, request: MEGARequest, completion: @escaping (Result) -> Void) { @@ -165,34 +214,44 @@ final class MeetingCreatingRepository: NSObject, MEGAChatDelegate, MeetingCreati } MEGALogDebug("Create meeting: open chat preview for url \(url)") - self.chatSdk.openChatPreview(url, delegate: MEGAChatResultRequestDelegate(completion: { [weak self] in - guard let self = self else { return } - switch $0 { + chatSdk.openChatPreview(url, delegate: ChatRequestDelegate { [weak self] result in + guard let self else { + completion(.failure(.unexpected)) + return + } + switch result { case .success(let chatRequest): MEGALogDebug("Create meeting: open chat preview succeeded with request \(chatRequest)") - self.chatResultDelegate = MEGAChatResultDelegate { _, chatId, newState in - if chatRequest.chatHandle == chatId, newState == .online, let chatResultDelegate = self.chatResultDelegate { - self.chatSdk.remove(chatResultDelegate) + chatResultDelegate = MEGAChatResultDelegate { [weak self] _, chatId, newState in + guard let self else { + completion(.failure(.unexpected)) + return + } + if chatRequest.chatHandle == chatId, newState == .online, let chatResultDelegate { + chatSdk.remove(chatResultDelegate) completion(.success(())) } } - if let chatResultDelegate = self.chatResultDelegate { - self.chatSdk.add(chatResultDelegate) + if let chatResultDelegate { + chatSdk.add(chatResultDelegate) } case .failure(let error): MEGALogDebug("Create meeting: open chat preview failure \(error)") completion(.failure(.unexpected)) } - })) + }) } private func answerCall(for chatRoom: ChatRoomEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) { MEGALogDebug("Create meeting: Answer call with chatroom id \(MEGASdk.base64Handle(forUserHandle: chatRoom.chatId) ?? "-1")") let answerCallDelegate = MEGAChatAnswerCallRequestDelegate { [weak self] (chatError) in - guard let self = self else { return } + guard let self else { + completion(.failure(.generic)) + return + } if chatError.type == .MEGAChatErrorTypeOk { - guard self.chatSdk.chatCall(forChatId: chatRoom.chatId) != nil else { + guard chatSdk.chatCall(forChatId: chatRoom.chatId) != nil else { MEGALogDebug("Create meeting: not able to find call with chat id \(MEGASdk.base64Handle(forUserHandle: chatRoom.chatId) ?? "-1")") completion(.failure(.generic)) return diff --git a/MEGAData/Repository/Chat/ChatRepository.swift b/MEGAData/Repository/Chat/ChatRepository.swift index 1698752a0d..6293d45c42 100644 --- a/MEGAData/Repository/Chat/ChatRepository.swift +++ b/MEGAData/Repository/Chat/ChatRepository.swift @@ -142,6 +142,13 @@ public final class ChatRepository: ChatRepositoryProtocol { return call.isCallInProgress } + public func isCallActive(for chatId: HandleEntity) -> Bool { + guard let call = chatSDK.chatCall(forChatId: chatId) else { + return false + } + return call.isActiveCall + } + public func myFullName() -> String? { chatSDK.myFullname } diff --git a/MEGAData/Repository/Chat/ExportChatMessagesRepository.swift b/MEGAData/Repository/Chat/ExportChatMessagesRepository.swift index 027028aaf0..f2e848b8e6 100644 --- a/MEGAData/Repository/Chat/ExportChatMessagesRepository.swift +++ b/MEGAData/Repository/Chat/ExportChatMessagesRepository.swift @@ -1,10 +1,9 @@ - import Foundation import MEGADomain final class ExportChatMessagesRepository: ExportChatMessagesRepositoryProtocol { static var newRepo: ExportChatMessagesRepository { - ExportChatMessagesRepository(sdk: MEGASdkManager.sharedMEGASdk(), chatSdk: MEGASdkManager.sharedMEGAChatSdk(), store: MEGAStore.shareInstance()) + ExportChatMessagesRepository(sdk: .shared, chatSdk: .shared, store: MEGAStore.shareInstance()) } private let sdk: MEGASdk diff --git a/MEGAData/Repository/Chat/ManageChatHistoryRepository.swift b/MEGAData/Repository/Chat/ManageChatHistoryRepository.swift deleted file mode 100644 index d1946efb8f..0000000000 --- a/MEGAData/Repository/Chat/ManageChatHistoryRepository.swift +++ /dev/null @@ -1,73 +0,0 @@ - -import Foundation -import MEGADomain - -struct ManageChatHistoryRepository: ManageChatHistoryRepositoryProtocol { - private let chatSdk: MEGAChatSdk - - init(chatSdk: MEGAChatSdk) { - self.chatSdk = chatSdk - } - - func chatRetentionTime(for chatId: ChatIdEntity, completion: @escaping (Result) -> Void) { - if let chatRoom = chatSdk.chatRoom(forChatId: chatId) { - completion(.success(chatRoom.retentionTime)) - } else { - completion(.failure(.generic)) - } - } - - func setChatRetentionTime(for chatId: ChatIdEntity, period: UInt, completion: @escaping (Result) -> Void) { - let delegate = MEGAChatGenericRequestDelegate { request, error in - if error.type == .MEGAChatErrorTypeOk { - let requestPeriod = UInt(truncatingIfNeeded: request.number) - completion(.success(requestPeriod)) - return - } - - let chatRetentionTimeError: ManageChatHistoryErrorEntity - switch error.type { - case .MEGAChatErrorTypeArgs: - chatRetentionTimeError = .chatIdInvalid - - case .MEGAChatErrorTypeNoEnt: - chatRetentionTimeError = .chatIdDoesNotExist - - case .MEGAChatErrorTypeAccess: - chatRetentionTimeError = .notEnoughPrivileges - - default: - chatRetentionTimeError = .generic - } - - completion(.failure(chatRetentionTimeError)) - } - - chatSdk.setChatRetentionTime(chatId, period: period, delegate: delegate) - } - - func clearChatHistory(for chatId: ChatIdEntity, completion: @escaping (Result) -> Void) { - chatSdk.clearChatHistory(chatId, delegate: MEGAChatGenericRequestDelegate { _, error in - let clearChatHistoryError: ManageChatHistoryErrorEntity - switch error.type { - case .MEGAChatErrorTypeOk: - completion(.success(())) - return - - case .MEGAChatErrorTypeArgs: - clearChatHistoryError = .chatIdInvalid - - case .MEGAChatErrorTypeNoEnt: - clearChatHistoryError = .chatIdDoesNotExist - - case .MEGAChatErrorTypeAccess: - clearChatHistoryError = .notEnoughPrivileges - - default: - clearChatHistoryError = .generic - } - - completion(.failure(clearChatHistoryError)) - }) - } -} diff --git a/MEGAData/Repository/Chat/ScheduledMeetingRepository.swift b/MEGAData/Repository/Chat/ScheduledMeetingRepository.swift index f96450bbf4..13e539de1b 100644 --- a/MEGAData/Repository/Chat/ScheduledMeetingRepository.swift +++ b/MEGAData/Repository/Chat/ScheduledMeetingRepository.swift @@ -153,7 +153,7 @@ public final class ScheduledMeetingRepository: ScheduledMeetingRepositoryProtoco // MARK: - Private methods - private func makeChatRequestDelegate(withCompletion completion: @escaping (Result) -> Void) -> ChatRequestDelegate { + private func makeChatRequestDelegate(withCompletion completion: @escaping (Result) -> Void) -> ChatRequestDelegate { ChatRequestDelegate { result in if case .success(let request) = result { guard let scheduledMeeting = request.scheduledMeetingList.first?.toScheduledMeetingEntity() else { diff --git a/MEGAData/Repository/Files/FilesSearchRepository.swift b/MEGAData/Repository/Files/FilesSearchRepository.swift index e02e61af66..bf3dac1303 100644 --- a/MEGAData/Repository/Files/FilesSearchRepository.swift +++ b/MEGAData/Repository/Files/FilesSearchRepository.swift @@ -93,7 +93,7 @@ final class FilesSearchRepository: NSObject, FilesSearchRepositoryProtocol, @unc supportCancel: Bool, sortOrderType: SortOrderEntity, formatType: NodeFormatEntity, - completion: @escaping (Result<[NodeEntity], Error>) -> Void) { + completion: @escaping (Result<[NodeEntity], any Error>) -> Void) { guard let parent = node?.toMEGANode(in: sdk) ?? sdk.rootNode else { return completion(.failure(NodeSearchResultErrorEntity.noDataAvailable)) } diff --git a/MEGAData/Repository/Node/NodeActionRepository.swift b/MEGAData/Repository/Node/NodeActionRepository.swift deleted file mode 100644 index 6b8d586ac8..0000000000 --- a/MEGAData/Repository/Node/NodeActionRepository.swift +++ /dev/null @@ -1,288 +0,0 @@ -import MEGADomain -import MEGASDKRepo - -struct NodeActionRepository: NodeActionRepositoryProtocol { - static var newRepo: NodeActionRepository { - NodeActionRepository(sdk: MEGASdk.shared) - } - - private let sdk: MEGASdk - - init(sdk: MEGASdk) { - self.sdk = sdk - } - - private func removeLink(for node: NodeEntity) async throws { - return try await withCheckedThrowingContinuation { continuation in - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - - guard let megaNode = node.toMEGANode(in: sdk) else { return } - - sdk.disableExport(megaNode, delegate: RequestDelegate { result in - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - - switch result { - case .failure(let error): - switch error.type { - case .apiEBusinessPastDue: - continuation.resume(throwing: RemoveLinkErrorEntity.businessExpired) - case .apiENoent: - continuation.resume(throwing: RemoveLinkErrorEntity.notFound) - default: - continuation.resume(throwing: RemoveLinkErrorEntity.generic) - } - case .success: - continuation.resume(with: .success) - } - }) - } - } - - func fetchnodes() async throws { - return try await withCheckedThrowingContinuation { continuation in - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - sdk.fetchNodes(with: RequestDelegate { result in - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - switch result { - case .success: - continuation.resume() - case .failure: - continuation.resume(throwing: GenericErrorEntity()) - } - }) - } - } - - func createFolder(name: String, parent: NodeEntity) async throws -> NodeEntity { - try await withCheckedThrowingContinuation { continuation in - guard let parentNode = sdk.node(forHandle: parent.handle) else { - continuation.resume(throwing: CreateFolderErrorEntity.generic) - return - } - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - sdk.createFolder(withName: name, parent: parentNode, delegate: RequestDelegate { result in - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - - switch result { - case .success(let request): - guard let node = sdk.node(forHandle: request.nodeHandle) else { - continuation.resume(throwing: CreateFolderErrorEntity.generic) - return - } - continuation.resume(returning: node.toNodeEntity()) - case .failure(let error): - if error.type == .apiEBusinessPastDue { - continuation.resume(throwing: CreateFolderErrorEntity.businessExpired) - } else { - continuation.resume(throwing: CreateFolderErrorEntity.generic) - } - } - }) - } - } - - func rename(node: NodeEntity, name: String) async throws -> NodeEntity { - try await withCheckedThrowingContinuation { continuation in - guard let megaNode = sdk.node(forHandle: node.handle) else { - continuation.resume(throwing: RenameNodeErrorEntity.generic) - return - } - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - sdk.renameNode(megaNode, newName: name, delegate: RequestDelegate { result in - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - - switch result { - case .success(let request): - guard let node = sdk.node(forHandle: request.nodeHandle) else { - continuation.resume(throwing: RenameNodeErrorEntity.generic) - return - } - continuation.resume(returning: node.toNodeEntity()) - case .failure(let error): - if error.type == .apiEBusinessPastDue { - continuation.resume(throwing: RenameNodeErrorEntity.businessExpired) - } else { - continuation.resume(throwing: RenameNodeErrorEntity.generic) - } - } - }) - } - } - - func trash(node: NodeEntity) async throws -> NodeEntity { - try await withCheckedThrowingContinuation { continuation in - guard let node = sdk.node(forHandle: node.handle), - let rubbishBinNode = sdk.rubbishNode else { - continuation.resume(throwing: MoveNodeErrorEntity.generic) - return - } - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - sdk.move(node, newParent: rubbishBinNode, delegate: RequestDelegate { result in - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - - switch result { - case .success(let request): - guard let node = sdk.node(forHandle: request.nodeHandle) else { - continuation.resume(throwing: MoveNodeErrorEntity.generic) - return - } - continuation.resume(returning: node.toNodeEntity()) - case .failure(let error): - if error.type == .apiEBusinessPastDue { - continuation.resume(throwing: MoveNodeErrorEntity.businessExpired) - } else { - continuation.resume(throwing: MoveNodeErrorEntity.generic) - } - } - }) - } - } - - func untrash(node: NodeEntity) async throws -> NodeEntity { - try await withCheckedThrowingContinuation { continuation in - guard let node = sdk.node(forHandle: node.handle), - sdk.isNode(inRubbish: node) == true, - let restoreNode = sdk.node(forHandle: node.restoreHandle), - sdk.isNode(inRubbish: restoreNode) == false else { - continuation.resume(throwing: MoveNodeErrorEntity.generic) - return - } - - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - sdk.move(node, newParent: restoreNode, delegate: RequestDelegate { result in - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - - switch result { - case .success(let request): - guard let node = sdk.node(forHandle: request.nodeHandle) else { - continuation.resume(throwing: MoveNodeErrorEntity.generic) - return - } - continuation.resume(returning: node.toNodeEntity()) - case .failure(let error): - if error.type == .apiEBusinessPastDue { - continuation.resume(throwing: MoveNodeErrorEntity.businessExpired) - } else { - continuation.resume(throwing: MoveNodeErrorEntity.generic) - } - } - }) - } - } - - func delete(node: NodeEntity) async throws { - guard let node = sdk.node(forHandle: node.handle), - sdk.isNode(inRubbish: node) == true else { - throw RemoveNodeErrorEntity.generic - } - return try await withCheckedThrowingContinuation { continuation in - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - sdk.remove(node, delegate: RequestDelegate { result in - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - switch result { - case .success: - continuation.resume() - case .failure(let error): - if error.type == .apiEMasterOnly { - continuation.resume(throwing: RemoveNodeErrorEntity.masterOnly) - } else { - continuation.resume(throwing: RemoveNodeErrorEntity.generic) - } - } - }) - } - } - - func move(node: NodeEntity, toParent: NodeEntity) async throws -> NodeEntity { - try await withCheckedThrowingContinuation { continuation in - guard let node = sdk.node(forHandle: node.handle), - let parent = sdk.node(forHandle: toParent.handle) else { - continuation.resume(throwing: MoveNodeErrorEntity.generic) - return - } - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - sdk.move(node, newParent: parent, delegate: RequestDelegate { result in - guard Task.isCancelled == false else { - continuation.resume(throwing: CancellationError()) - return - } - - switch result { - case .success(let request): - guard let node = sdk.node(forHandle: request.nodeHandle) else { - continuation.resume(throwing: MoveNodeErrorEntity.generic) - return - } - continuation.resume(returning: node.toNodeEntity()) - case .failure(let error): - if error.type == .apiEBusinessPastDue { - continuation.resume(throwing: MoveNodeErrorEntity.businessExpired) - } else { - continuation.resume(throwing: MoveNodeErrorEntity.generic) - } - } - }) - } - } - - func removeLink(nodes: [NodeEntity]) async throws { - try await withThrowingTaskGroup(of: Void.self) { taskGroup in - guard taskGroup.isCancelled == false else { - throw CancellationError() - } - - nodes.forEach { node in - taskGroup.addTask { - try await removeLink(for: node) - } - } - - try await taskGroup.waitForAll() - } - } -} diff --git a/MEGAData/Repository/Photos/PhotoLibraryRepository.swift b/MEGAData/Repository/Photos/PhotoLibraryRepository.swift index be682dfd4b..4c53c44ea0 100644 --- a/MEGAData/Repository/Photos/PhotoLibraryRepository.swift +++ b/MEGAData/Repository/Photos/PhotoLibraryRepository.swift @@ -49,7 +49,7 @@ struct PhotoLibraryRepository: PhotoLibraryRepositoryProtocol, Sendable { return try await mediaUploadNode() } } - + private func cameraUploadNode() async throws -> NodeEntity? { try await withCheckedThrowingContinuation { continuation in CameraUploadNodeAccess.shared.loadNode { node, error in @@ -58,7 +58,7 @@ struct PhotoLibraryRepository: PhotoLibraryRepositoryProtocol, Sendable { continuation.resume(throwing: CancellationError()) return } - + if let node { continuation.resume(returning: node.toNodeEntity()) } else if let error = error { @@ -79,7 +79,7 @@ struct PhotoLibraryRepository: PhotoLibraryRepositoryProtocol, Sendable { continuation.resume(throwing: CancellationError()) return } - + if let node { continuation.resume(returning: node.toNodeEntity()) } else if let error { diff --git a/MEGAData/Repository/Transfer/DownloadFileRepository.swift b/MEGAData/Repository/Transfer/DownloadFileRepository.swift index ad6e2ea585..1e6b5d2946 100644 --- a/MEGAData/Repository/Transfer/DownloadFileRepository.swift +++ b/MEGAData/Repository/Transfer/DownloadFileRepository.swift @@ -1,21 +1,61 @@ import Foundation import MEGADomain import MEGASDKRepo +import MEGASwift struct DownloadFileRepository: DownloadFileRepositoryProtocol { + static var newRepo: DownloadFileRepository { - DownloadFileRepository(sdk: MEGASdkManager.sharedMEGASdk(), sharedFolderSdk: nil, chatSdk: MEGASdkManager.sharedMEGAChatSdk()) + let sdk = MEGASdk.sharedSdk + return DownloadFileRepository( + sdk: sdk, + sharedFolderSdk: nil, + chatSdk: .sharedChatSdk, + nodeProvider: DefaultMEGANodeProvider(sdk: sdk)) } private let sdk: MEGASdk private let sharedFolderSdk: MEGASdk? private let chatSdk: MEGAChatSdk + private let nodeProvider: any MEGANodeProviderProtocol private let cancelToken = MEGACancelToken() - init(sdk: MEGASdk, sharedFolderSdk: MEGASdk? = nil, chatSdk: MEGAChatSdk = MEGASdkManager.sharedMEGAChatSdk()) { + init(sdk: MEGASdk, sharedFolderSdk: MEGASdk? = nil, chatSdk: MEGAChatSdk = .sharedChatSdk, nodeProvider: some MEGANodeProviderProtocol = DefaultMEGANodeProvider(sdk: .sharedSdk)) { self.sdk = sdk self.sharedFolderSdk = sharedFolderSdk self.chatSdk = chatSdk + self.nodeProvider = nodeProvider + } + + func download(nodeHandle: HandleEntity, to url: URL, metaData: TransferMetaDataEntity?) async throws -> TransferEntity { + let megaNode: MEGANode + + if let sharedFolderSdk = sharedFolderSdk { + guard let node = sharedFolderSdk.node(forHandle: nodeHandle), + let sharedNode = sharedFolderSdk.authorizeNode(node) else { + throw TransferErrorEntity.couldNotFindNodeByHandle + } + megaNode = sharedNode + } else { + guard let node = await nodeProvider.node(for: nodeHandle) else { + throw TransferErrorEntity.couldNotFindNodeByHandle + } + megaNode = node + } + + return try await withAsyncThrowingValue { continuation in + sdk.startDownloadNode( + megaNode, + localPath: url.path, + fileName: nil, + appData: metaData?.rawValue, + startFirst: true, + cancelToken: cancelToken, + collisionCheck: CollisionCheck.fingerprint, + collisionResolution: CollisionResolution.newWithN, + delegate: TransferDelegate(completion: { result in continuation(result.mapError { $0 }) }) + ) + } } func download(nodeHandle: HandleEntity, to url: URL, metaData: TransferMetaDataEntity?, completion: @escaping (Result) -> Void) { diff --git a/MEGAData/Repository/User/UserInviteRepository.swift b/MEGAData/Repository/User/UserInviteRepository.swift deleted file mode 100644 index fb19ec51fa..0000000000 --- a/MEGAData/Repository/User/UserInviteRepository.swift +++ /dev/null @@ -1,14 +0,0 @@ -import MEGADomain - -struct UserInviteRepository: UserInviteRepositoryProtocol { - private let sdk: MEGASdk - - init(sdk: MEGASdk) { - self.sdk = sdk - } - - func sendInvite(forEmail email: String, - completion: @escaping (Result) -> Void) { - sdk.inviteContact(withEmail: email, message: "", action: .add, delegate: InviteRequestDelegate(completion: completion)) - } -} diff --git a/MEGAData/SDK/Extensions/MEGANode+Additions.swift b/MEGAData/SDK/Extensions/MEGANode+Additions.swift index 7bd784a218..969ac7b2f1 100644 --- a/MEGAData/SDK/Extensions/MEGANode+Additions.swift +++ b/MEGAData/SDK/Extensions/MEGANode+Additions.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n import MEGASdk import MEGASDKRepo import MEGASwift diff --git a/MEGAData/SDK/Extensions/MEGANode+FilePath.swift b/MEGAData/SDK/Extensions/MEGANode+FilePath.swift index b1ce94f43f..05b520fc0c 100644 --- a/MEGAData/SDK/Extensions/MEGANode+FilePath.swift +++ b/MEGAData/SDK/Extensions/MEGANode+FilePath.swift @@ -1,4 +1,3 @@ - import Foundation extension MEGANode { diff --git a/MEGAData/SDK/Model Mapping/Chat/Call/CallEntity+Mapper.swift b/MEGAData/SDK/Model Mapping/Chat/Call/CallEntity+Mapper.swift index 289602aed5..f02e4a1a6a 100644 --- a/MEGAData/SDK/Model Mapping/Chat/Call/CallEntity+Mapper.swift +++ b/MEGAData/SDK/Model Mapping/Chat/Call/CallEntity+Mapper.swift @@ -12,7 +12,7 @@ fileprivate extension CallEntity { self.init(status: call.status.toCallStatusType(), chatId: call.chatId, callId: call.callId, - changeTye: call.changes.toChangeTypeEntity(), + changeType: call.changes.toChangeTypeEntity(), duration: call.duration, initialTimestamp: call.initialTimeStamp, finalTimestamp: call.finalTimeStamp, @@ -26,6 +26,9 @@ fileprivate extension CallEntity { sessionClientIds: sessionClientIds, clientSessions: sessionClientIds.compactMap { call.session(forClientId: UInt64($0))?.toChatSessionEntity() }, participants: (0.. CallEntity.ChangeType { switch self { - case .noChages: + case .noChanges: return .noChanges case .status: return .status @@ -113,6 +116,22 @@ extension MEGAChatCallChangeType { return .ownPermission case .genericNotification: return .genericNotification + case .waitingRoomAllow: + return .waitingRoomAllow + case .waitingRoomDeny: + return .waitingRoomDeny + case .waitingRoomComposition: + return .waitingRoomComposition + case .waitingRoomUsersEntered: + return .waitingRoomUsersEntered + case .waitingRoomUsersLeave: + return .waitingRoomUsersLeave + case .waitingRoomUsersAllow: + return .waitingRoomUsersAllow + case .waitingRoomUsersDeny: + return .waitingRoomUsersDeny + case .waitingRoomPushedFromCall: + return .waitingRoomPushedFromCall @unknown default: return .noChanges } @@ -141,6 +160,8 @@ extension MEGAChatCallNotificationType { return .invalid case .sfuError: return .serverError + case .sfuDeny: + return .sfuDeny @unknown default: return .invalid } diff --git a/MEGAData/SDK/Model Mapping/Chat/Call/WaitingRoomEntity+Mapper.swift b/MEGAData/SDK/Model Mapping/Chat/Call/WaitingRoomEntity+Mapper.swift new file mode 100644 index 0000000000..92dd5d636d --- /dev/null +++ b/MEGAData/SDK/Model Mapping/Chat/Call/WaitingRoomEntity+Mapper.swift @@ -0,0 +1,28 @@ +import MEGADomain + +extension MEGAChatWaitingRoom { + func toWaitingRoomEntity() -> WaitingRoomEntity { + WaitingRoomEntity(with: self) + } +} + +extension MEGAChatWaitingRoomStatus { + func toWaitingRoomStatusEntity() -> WaitingRoomStatus { + switch self { + case .unknown: + return .unknown + case .notAllowed: + return .notAllowed + case .allowed: + return .allowed + @unknown default: + return .unknown + } + } +} + +fileprivate extension WaitingRoomEntity { + init(with waitingRoom: MEGAChatWaitingRoom) { + self.init(sessionClientIds: waitingRoom.peers?.toHandleEntityArray() ?? []) + } +} diff --git a/MEGAData/SDK/Model Mapping/Chat/ChatListItemEntity+Mapper.swift b/MEGAData/SDK/Model Mapping/Chat/ChatListItemEntity+Mapper.swift index 0adcd2a85f..30746537da 100644 --- a/MEGAData/SDK/Model Mapping/Chat/ChatListItemEntity+Mapper.swift +++ b/MEGAData/SDK/Model Mapping/Chat/ChatListItemEntity+Mapper.swift @@ -1,4 +1,3 @@ - import MEGADomain extension MEGAChatListItem { diff --git a/MEGAData/SDK/Model Mapping/User/UserAttributeEntity+Mapper.swift b/MEGAData/SDK/Model Mapping/User/UserAttributeEntity+Mapper.swift deleted file mode 100644 index e7414cb193..0000000000 --- a/MEGAData/SDK/Model Mapping/User/UserAttributeEntity+Mapper.swift +++ /dev/null @@ -1,39 +0,0 @@ -import MEGADomain - -extension UserAttributeEntity { - func toMEGAUserAttribute() -> MEGAUserAttribute { - switch self { - case .avatar: return MEGAUserAttribute.avatar - case .firstName: return MEGAUserAttribute.firstname - case .lastName: return MEGAUserAttribute.lastname - case .authRing: return MEGAUserAttribute.authRing - case .lastInteraction: return MEGAUserAttribute.lastInteraction - case .eD25519PublicKey: return MEGAUserAttribute.ed25519PublicKey - case .cU25519PublicKey: return MEGAUserAttribute.cu25519PublicKey - case .keyring: return MEGAUserAttribute.keyring - case .sigRsaPublicKey: return MEGAUserAttribute.sigRsaPublicKey - case .sigCU255PublicKey: return MEGAUserAttribute.sigCU255PublicKey - case .language: return MEGAUserAttribute.language - case .pwdReminder: return MEGAUserAttribute.pwdReminder - case .disableVersions: return MEGAUserAttribute.disableVersions - case .contactLinkVerification: return MEGAUserAttribute.contactLinkVerification - case .richPreviews: return MEGAUserAttribute.richPreviews - case .rubbishTime: return MEGAUserAttribute.rubbishTime - case .lastPSA: return MEGAUserAttribute.lastPSA - case .storageState: return MEGAUserAttribute.storageState - case .geolocation: return MEGAUserAttribute.geolocation - case .cameraUploadsFolder: return MEGAUserAttribute.cameraUploadsFolder - case .myChatFilesFolder: return MEGAUserAttribute.myChatFilesFolder - case .pushSettings: return MEGAUserAttribute.pushSettings - case .alias: return MEGAUserAttribute.alias - case .deviceNames: return MEGAUserAttribute.deviceNames - case .backupsFolder: return MEGAUserAttribute.backupsFolder - case .cookieSettings: return MEGAUserAttribute.cookieSettings - case .jsonSyncConfigData: return MEGAUserAttribute.jsonSyncConfigData - case .drivesName: return MEGAUserAttribute.drivesName - case .noCallKit: return MEGAUserAttribute.noCallKit - case .appsPreferences: return MEGAUserAttribute.appsPreferences - case .contentConsumptionPreferences: return MEGAUserAttribute.contentConsumptionPreferences - } - } -} diff --git a/MEGAData/SDK/RequestDelegate/InviteRequestDelegate.swift b/MEGAData/SDK/RequestDelegate/InviteRequestDelegate.swift deleted file mode 100644 index bc5a8c21fa..0000000000 --- a/MEGAData/SDK/RequestDelegate/InviteRequestDelegate.swift +++ /dev/null @@ -1,36 +0,0 @@ -import MEGADomain - -final class InviteRequestDelegate: NSObject, MEGARequestDelegate { - typealias Completion = (_ result: Result) -> Void - private let completion: Completion - - init(completion: @escaping Completion) { - self.completion = completion - } - - func onRequestFinish(_ api: MEGASdk, request: MEGARequest, error: MEGAError) { - if error.type == .apiOk { - completion(.success(())) - } else { - switch error.type { - case .apiEArgs where request.email == MEGASdk.currentUserEmail: - completion(.failure(.ownEmailEntered)) - case .apiEExist: - if let user = api.contact(forEmail: request.email), user.visibility == .visible { - completion(.failure(.alreadyAContact)) - } else { - let outgoingContactRequests = api.outgoingContactRequests() - let contactRequests = (0..: NSObject, MEGARequestDelegate { - - private let completion: ((Result) -> Void)? - - private let mapValue: (MEGARequest) -> ResultValue - - private let mapError: (MEGASDKErrorType) -> ResultError - - init( - completion: ((Result) -> Void)?, - mapValue: @escaping (MEGARequest) -> ResultValue, - mapError: @escaping (MEGASDKErrorType) -> ResultError - ) { - self.completion = completion - self.mapValue = mapValue - self.mapError = mapError - } - - func onRequestFinish(_ api: MEGASdk, request: MEGARequest, error: MEGAError) { - if let sdkError = error.sdkError { - completion?(.failure(mapError(sdkError))) - return - } - - completion?(.success(mapValue(request))) - } -} diff --git a/MEGADataTests/Mocks/MEGAPurchase/MockMEGAPurchase.swift b/MEGADataTests/Mocks/MEGAPurchase/MockMEGAPurchase.swift index 0f122b8874..e4c3aaf6c1 100644 --- a/MEGADataTests/Mocks/MEGAPurchase/MockMEGAPurchase.swift +++ b/MEGADataTests/Mocks/MEGAPurchase/MockMEGAPurchase.swift @@ -1,4 +1,3 @@ - final class MockMEGAPurchase: MEGAPurchase { private(set) var restorePurchaseCalled = 0 private(set) var purchasePlanCalled = 0 diff --git a/MEGADataTests/Repos/AccountPlanPurchaseRepositoryTests.swift b/MEGADataTests/Repos/AccountPlanPurchaseRepositoryTests.swift index f0df081b31..33933b2a0b 100644 --- a/MEGADataTests/Repos/AccountPlanPurchaseRepositoryTests.swift +++ b/MEGADataTests/Repos/AccountPlanPurchaseRepositoryTests.swift @@ -12,10 +12,10 @@ final class AccountPlanPurchaseRepositoryTests: XCTestCase { MockSKProduct(identifier: "pro2.oneMonth", price: "1", priceLocale: Locale.current), MockSKProduct(identifier: "pro3.oneMonth", price: "1", priceLocale: Locale.current), MockSKProduct(identifier: "lite.oneMonth", price: "1", priceLocale: Locale.current)] - let expectedResult = [AccountPlanEntity(type: .proI, term: .monthly), - AccountPlanEntity(type: .proII, term: .monthly), - AccountPlanEntity(type: .proIII, term: .monthly), - AccountPlanEntity(type: .lite, term: .monthly)] + let expectedResult = [AccountPlanEntity(type: .proI, subscriptionCycle: .monthly), + AccountPlanEntity(type: .proII, subscriptionCycle: .monthly), + AccountPlanEntity(type: .proIII, subscriptionCycle: .monthly), + AccountPlanEntity(type: .lite, subscriptionCycle: .monthly)] let mockPurchase = MockMEGAPurchase(productPlans: products) let sut = AccountPlanPurchaseRepository(purchase: mockPurchase) @@ -28,10 +28,10 @@ final class AccountPlanPurchaseRepositoryTests: XCTestCase { MockSKProduct(identifier: "pro2.oneYear", price: "1", priceLocale: Locale.current), MockSKProduct(identifier: "pro3.oneYear", price: "1", priceLocale: Locale.current), MockSKProduct(identifier: "lite.oneYear", price: "1", priceLocale: Locale.current)] - let expectedResult = [AccountPlanEntity(type: .proI, term: .yearly), - AccountPlanEntity(type: .proII, term: .yearly), - AccountPlanEntity(type: .proIII, term: .yearly), - AccountPlanEntity(type: .lite, term: .yearly)] + let expectedResult = [AccountPlanEntity(type: .proI, subscriptionCycle: .yearly), + AccountPlanEntity(type: .proII, subscriptionCycle: .yearly), + AccountPlanEntity(type: .proIII, subscriptionCycle: .yearly), + AccountPlanEntity(type: .lite, subscriptionCycle: .yearly)] let mockPurchase = MockMEGAPurchase(productPlans: products) let sut = AccountPlanPurchaseRepository(purchase: mockPurchase) diff --git a/MEGADataTests/Repos/DownloadFileRepositoryTests.swift b/MEGADataTests/Repos/DownloadFileRepositoryTests.swift new file mode 100644 index 0000000000..c548f22e71 --- /dev/null +++ b/MEGADataTests/Repos/DownloadFileRepositoryTests.swift @@ -0,0 +1,56 @@ +import Foundation +@testable import MEGA +import MEGADomain +import MEGADomainMock +import MEGASDKRepo +import MEGASDKRepoMock +import XCTest + +class DownloadFileRepositoryTests: XCTestCase { + + func testDownloadNodeHandle_shouldUseNodeProvider() async throws { + // Arrange + let node = MockNode(handle: 1) + let sdk = MockSdk(nodes: [node]) + let nodeProvider = MockMEGANodeProvider(node: node) + let sut = makeSUT(sdk: sdk, nodeProvider: nodeProvider) + + // Act + let result = try await sut.download(nodeHandle: node.handle, to: URL(fileURLWithPath: "/path"), metaData: .saveInPhotos) + + // Assert + XCTAssertEqual(sdk.nodeForHandleCallCount, 0) + XCTAssertEqual(sdk.authorizeNodeCalled, 0) + XCTAssertEqual(nodeProvider.nodeForHandleCallCount, 1) + XCTAssertEqual(result.nodeHandle, node.handle) + } + + func testDownloadNodeHandle_forFolderLinks_shouldUseCorrectFolderLinksSDK() async throws { + // Arrange + let node = MockNode(handle: 1) + let sdk = MockSdk() + let folderLinksSDK = MockSdk(nodes: [node]) + let nodeProvider = MockMEGANodeProvider() + let sut = makeSUT(sdk: sdk, folderLinksSDK: folderLinksSDK, nodeProvider: nodeProvider) + + // Act + let result = try await sut.download(nodeHandle: node.handle, to: URL(fileURLWithPath: "/path"), metaData: .saveInPhotos) + + // Assert + XCTAssertEqual(sdk.nodeForHandleCallCount, 0) + XCTAssertEqual(folderLinksSDK.nodeForHandleCallCount, 1) + XCTAssertEqual(folderLinksSDK.authorizeNodeCalled, 1) + XCTAssertEqual(nodeProvider.nodeForHandleCallCount, 0) + XCTAssertEqual(result.nodeHandle, node.handle) + } + + func makeSUT(sdk: MEGASdk = MockSdk(), + folderLinksSDK: MEGASdk? = nil, + nodeProvider: any MEGANodeProviderProtocol = MockMEGANodeProvider()) -> DownloadFileRepository { + DownloadFileRepository( + sdk: sdk, + sharedFolderSdk: folderLinksSDK, + chatSdk: MockChatSDK(chatRoom: nil), + nodeProvider: nodeProvider) + } +} diff --git a/MEGADataTests/Repos/NodeActionRepositoryTests.swift b/MEGADataTests/Repos/NodeActionRepositoryTests.swift index 1c2c4c7553..505e2aed0c 100644 --- a/MEGADataTests/Repos/NodeActionRepositoryTests.swift +++ b/MEGADataTests/Repos/NodeActionRepositoryTests.swift @@ -1,6 +1,6 @@ - @testable import MEGA import MEGADomain +import MEGASDKRepo import MEGASDKRepoMock import XCTest diff --git a/MEGADataTests/SDK/Extensions/MEGANode+Additions_Tests.swift b/MEGADataTests/SDK/Extensions/MEGANode+Additions_Tests.swift index b1d6aa76cd..9613e309f1 100644 --- a/MEGADataTests/SDK/Extensions/MEGANode+Additions_Tests.swift +++ b/MEGADataTests/SDK/Extensions/MEGANode+Additions_Tests.swift @@ -1,4 +1,3 @@ - @testable import MEGA import MEGASDKRepoMock import XCTest diff --git a/MEGADomain/Entity/Call/EndCallDialogType.swift b/MEGADomain/Entity/Call/EndCallDialogType.swift index 9c82f66786..dc4021c959 100644 --- a/MEGADomain/Entity/Call/EndCallDialogType.swift +++ b/MEGADomain/Entity/Call/EndCallDialogType.swift @@ -1,3 +1,4 @@ +import MEGAL10n enum EndCallDialogType { case endCallForMyself diff --git a/MEGADomain/RepositoryProtocol/Calls/MeetingCreatingRepositoryProtocol.swift b/MEGADomain/RepositoryProtocol/Calls/MeetingCreatingRepositoryProtocol.swift index 422591b56d..32e8bb8173 100644 --- a/MEGADomain/RepositoryProtocol/Calls/MeetingCreatingRepositoryProtocol.swift +++ b/MEGADomain/RepositoryProtocol/Calls/MeetingCreatingRepositoryProtocol.swift @@ -5,6 +5,7 @@ protocol MeetingCreatingRepositoryProtocol { func getCall(forChatId chatId: UInt64) -> CallEntity? func startCall(_ startCall: StartCallEntity, completion: @escaping (Result) -> Void) func joinChatCall(forChatId chatId: UInt64, enableVideo: Bool, enableAudio: Bool, userHandle: UInt64, completion: @escaping (Result) -> Void) + func joinChatWithoutAnswerCall(forChatId chatId: UInt64, userHandle: UInt64, completion: @escaping (Result) -> Void) func checkChatLink(link: String, completion: @escaping (Result) -> Void) func createEphemeralAccountAndJoinChat(firstName: String, lastName: String, link: String, completion: @escaping (Result) -> Void, karereInitCompletion: @escaping () -> Void) func createChatLink(forChatId chatId: UInt64) diff --git a/MEGADomain/RepositoryProtocol/Calls/MeetingNoUserJoinedRepositoryProtocol.swift b/MEGADomain/RepositoryProtocol/Calls/MeetingNoUserJoinedRepositoryProtocol.swift index f2b93b62b2..cb80aafcd4 100644 --- a/MEGADomain/RepositoryProtocol/Calls/MeetingNoUserJoinedRepositoryProtocol.swift +++ b/MEGADomain/RepositoryProtocol/Calls/MeetingNoUserJoinedRepositoryProtocol.swift @@ -1,4 +1,3 @@ - import Combine import MEGADomain diff --git a/MEGADomain/UseCase/Calls/CallLocalVideoUseCase.swift b/MEGADomain/UseCase/Calls/CallLocalVideoUseCase.swift index d488834722..5579abc195 100644 --- a/MEGADomain/UseCase/Calls/CallLocalVideoUseCase.swift +++ b/MEGADomain/UseCase/Calls/CallLocalVideoUseCase.swift @@ -6,7 +6,7 @@ protocol CallLocalVideoUseCaseProtocol { func addLocalVideo(for chatId: HandleEntity, callbacksDelegate: some CallLocalVideoCallbacksUseCaseProtocol) func removeLocalVideo(for chatId: HandleEntity, callbacksDelegate: some CallLocalVideoCallbacksUseCaseProtocol) func videoDeviceSelected() -> String? - func selectCamera(withLocalizedName localizedName: String, completion: @escaping (Result) -> Void) + func selectCamera(withLocalizedName localizedName: String, completion: @escaping (Result) -> Void) func openVideoDevice(completion: @escaping (Result) -> Void) func releaseVideoDevice(completion: @escaping (Result) -> Void) } @@ -47,7 +47,7 @@ final class CallLocalVideoUseCase: NSObject repository.videoDeviceSelected() } - func selectCamera(withLocalizedName localizedName: String, completion: @escaping (Result) -> Void) { + func selectCamera(withLocalizedName localizedName: String, completion: @escaping (Result) -> Void) { repository.selectCamera(withLocalizedName: localizedName, completion: completion) } diff --git a/MEGADomain/UseCase/Calls/CallUseCase.swift b/MEGADomain/UseCase/Calls/CallUseCase.swift index 24e4569142..ea0f850a21 100644 --- a/MEGADomain/UseCase/Calls/CallUseCase.swift +++ b/MEGADomain/UseCase/Calls/CallUseCase.swift @@ -9,12 +9,17 @@ protocol CallUseCaseProtocol { func startCall(for chatId: HandleEntity, enableVideo: Bool, enableAudio: Bool) async throws -> CallEntity func startCallNoRinging(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) func startCallNoRinging(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool) async throws -> CallEntity + func startMeetingInWaitingRoomChat(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) + func startMeetingInWaitingRoomChat(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool) async throws -> CallEntity func joinCall(for chatId: HandleEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) func createActiveSessions() func hangCall(for callId: HandleEntity) func endCall(for callId: HandleEntity) func addPeer(toCall call: CallEntity, peerId: HandleEntity) func removePeer(fromCall call: CallEntity, peerId: HandleEntity) + func allowUsersJoinCall(_ call: CallEntity, users: [HandleEntity]) + func kickUsersFromCall(_ call: CallEntity, users: [HandleEntity]) + func pushUsersIntoWaitingRoom(for scheduledMeeting: ScheduledMeetingEntity, users: [HandleEntity]) func makePeerAModerator(inCall call: CallEntity, peerId: HandleEntity) func removePeerAsModerator(inCall call: CallEntity, peerId: HandleEntity) } @@ -22,6 +27,8 @@ protocol CallUseCaseProtocol { protocol CallCallbacksUseCaseProtocol: AnyObject { func participantJoined(participant: CallParticipantEntity) func participantLeft(participant: CallParticipantEntity) + func waitingRoomUsersEntered(with handles: [HandleEntity]) + func waitingRoomUsersLeave(with handles: [HandleEntity]) func updateParticipant(_ participant: CallParticipantEntity) func remoteVideoResolutionChanged(for participant: CallParticipantEntity) func highResolutionChanged(for participant: CallParticipantEntity) @@ -99,6 +106,14 @@ final class CallUseCase: NSObject, CallUseCaseProtoco try await repository.startCallNoRinging(for: scheduledMeeting, enableVideo: enableVideo, enableAudio: enableAudio) } + func startMeetingInWaitingRoomChat(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) { + repository.startMeetingInWaitingRoomChat(for: scheduledMeeting, enableVideo: enableVideo, enableAudio: enableAudio, completion: completion) + } + + func startMeetingInWaitingRoomChat(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool) async throws -> CallEntity { + try await repository.startMeetingInWaitingRoomChat(for: scheduledMeeting, enableVideo: enableVideo, enableAudio: enableAudio) + } + func joinCall(for chatId: HandleEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) { repository.joinCall(for: chatId, enableVideo: enableVideo, enableAudio: true, completion: completion) } @@ -123,6 +138,18 @@ final class CallUseCase: NSObject, CallUseCaseProtoco repository.removePeer(fromCall: call, peerId: peerId) } + func allowUsersJoinCall(_ call: CallEntity, users: [HandleEntity]) { + repository.allowUsersJoinCall(call, users: users) + } + + func kickUsersFromCall(_ call: CallEntity, users: [HandleEntity]) { + repository.kickUsersFromCall(call, users: users) + } + + func pushUsersIntoWaitingRoom(for scheduledMeeting: ScheduledMeetingEntity, users: [HandleEntity]) { + repository.pushUsersIntoWaitingRoom(for: scheduledMeeting, users: users) + } + func makePeerAModerator(inCall call: CallEntity, peerId: HandleEntity) { repository.makePeerAModerator(inCall: call, peerId: peerId) } @@ -197,4 +224,12 @@ extension CallUseCase: CallCallbacksRepositoryProtocol { func outgoingRingingStopReceived() { callbacksDelegate?.outgoingRingingStopReceived() } + + func waitingRoomUsersEntered(with handles: [HandleEntity]) { + callbacksDelegate?.waitingRoomUsersEntered(with: handles) + } + + func waitingRoomUsersLeave(with handles: [HandleEntity]) { + callbacksDelegate?.waitingRoomUsersLeave(with: handles) + } } diff --git a/MEGADomain/UseCase/Calls/MeetingCreatingUseCase.swift b/MEGADomain/UseCase/Calls/MeetingCreatingUseCase.swift index 15708756f5..1520ba63a2 100644 --- a/MEGADomain/UseCase/Calls/MeetingCreatingUseCase.swift +++ b/MEGADomain/UseCase/Calls/MeetingCreatingUseCase.swift @@ -4,6 +4,7 @@ import MEGADomain protocol MeetingCreatingUseCaseProtocol { func startCall(_ startCall: StartCallEntity, completion: @escaping (Result) -> Void) func joinCall(forChatId chatId: UInt64, enableVideo: Bool, enableAudio: Bool, userHandle: UInt64, completion: @escaping (Result) -> Void) + func joinChat(forChatId chatId: UInt64, userHandle: UInt64, completion: @escaping (Result) -> Void) func getUsername() -> String func getCall(forChatId chatId: UInt64) -> CallEntity? func createEphemeralAccountAndJoinChat(firstName: String, lastName: String, link: String, completion: @escaping (Result) -> Void, karereInitCompletion: @escaping () -> Void) @@ -28,6 +29,10 @@ struct MeetingCreatingUseCase: MeetingCrea repository.joinChatCall(forChatId: chatId, enableVideo: enableVideo, enableAudio: enableAudio, userHandle: userHandle, completion: completion) } + func joinChat(forChatId chatId: UInt64, userHandle: UInt64, completion: @escaping (Result) -> Void) { + repository.joinChatWithoutAnswerCall(forChatId: chatId, userHandle: userHandle, completion: completion) + } + func getUsername() -> String { repository.getUsername() } diff --git a/MEGADomain/UseCase/User/UserImageUseCase.swift b/MEGADomain/UseCase/User/UserImageUseCase.swift index 6b8d634a3f..a0d9f96aaf 100644 --- a/MEGADomain/UseCase/User/UserImageUseCase.swift +++ b/MEGADomain/UseCase/User/UserImageUseCase.swift @@ -76,9 +76,14 @@ struct UserImageUseCase AlbumCellViewModel { + let sut = AlbumCellViewModel(thumbnailUseCase: thumbnailUseCase, + album: album, + selection: selection, + featureFlagProvider: featureFlagProvider, + tracker: tracker) + trackForMemoryLeaks(on: sut, file: file, line: line) + return sut + } } diff --git a/MEGAUnitTests/Album/AlbumContentViewModelTests.swift b/MEGAUnitTests/Album/AlbumContentViewModelTests.swift index 1ef90208f9..73e5d4c807 100644 --- a/MEGAUnitTests/Album/AlbumContentViewModelTests.swift +++ b/MEGAUnitTests/Album/AlbumContentViewModelTests.swift @@ -1,25 +1,31 @@ import Combine @testable import MEGA +import MEGAAnalyticsiOS import MEGADomain import MEGADomainMock +import MEGAL10n +import MEGAPresentation import XCTest final class AlbumContentViewModelTests: XCTestCase { private let albumEntity = AlbumEntity(id: 1, name: "GIFs", coverNode: NodeEntity(handle: 1), count: 2, type: .gif) - private let router = MockAlbumContentRouting() - - func testDispatchViewReady_onLoadedNodesSuccessfully_shouldReturnNodesForAlbum() { + func testDispatchViewReady_onLoadedNodesSuccessfully_shouldReturnNodesForAlbumAndTrackScreenEvent() { + let tracker = MockTracker() let expectedNodes = [NodeEntity(name: "sample1.gif", handle: 1), NodeEntity(name: "sample2.gif", handle: 2)] - let sut = AlbumContentViewModel(album: albumEntity, - albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: albumEntity, + albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities()), + tracker: tracker) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: expectedNodes, sortOrder: .newest)]) + + tracker.assertTrackAnalyticsEventCalled( + with: [ + AlbumContentScreenEvent() + ] + ) } func testDispatchViewReady_onLoadedNodesSuccessfully_shouldSortAndThenReturnNodesForFavouritesAlbum() throws { @@ -27,33 +33,24 @@ final class AlbumContentViewModelTests: XCTestCase { NodeEntity(name: "sample2.gif", handle: 3, modificationTime: try "2022-12-3T20:01:04Z".date), NodeEntity(name: "sample1.gif", handle: 2, modificationTime: try "2022-08-19T20:01:04Z".date), NodeEntity(name: "sample2.gif", handle: 1, modificationTime: try "2022-08-19T20:01:04Z".date)] - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: NodeEntity(handle: 1), count: 2, type: .favourite), - albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: NodeEntity(handle: 1), count: 2, type: .favourite), + albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities())) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: expectedNodes, sortOrder: .newest)]) } func testDispatchViewReady_onLoadedNodesEmptyForFavouritesAlbum_shouldShowEmptyAlbum() { - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: nil, count: 0, type: .favourite), - albumContentsUseCase: MockAlbumContentUseCase(photos: []), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: nil, count: 0, type: .favourite), + albumContentsUseCase: MockAlbumContentUseCase(photos: [])) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: [], sortOrder: .newest)]) XCTAssertNil(sut.contextMenuConfiguration) } func testDispatchViewReady_onLoadedNodesEmpty_albumNilShouldDismiss() { - let sut = AlbumContentViewModel(album: albumEntity, - albumContentsUseCase: MockAlbumContentUseCase(photos: []), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: albumEntity, + albumContentsUseCase: MockAlbumContentUseCase(photos: [])) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.dismissAlbum]) } @@ -61,13 +58,10 @@ final class AlbumContentViewModelTests: XCTestCase { let nodesToAdd = [NodeEntity(handle: 1), NodeEntity(handle: 2)] let resultEntity = AlbumElementsResultEntity(success: UInt(nodesToAdd.count), failure: 0) let albumModificationUseCase = MockAlbumModificationUseCase(addPhotosResult: .success(resultEntity)) - let sut = AlbumContentViewModel(album: albumEntity, - albumContentsUseCase: MockAlbumContentUseCase(photos: nodesToAdd.toAlbumPhotoEntities()), - albumModificationUseCase: albumModificationUseCase, - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, - newAlbumPhotosToAdd: nodesToAdd, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: albumEntity, + albumContentsUseCase: MockAlbumContentUseCase(photos: nodesToAdd.toAlbumPhotoEntities()), + albumModificationUseCase: albumModificationUseCase, + newAlbumPhotosToAdd: nodesToAdd) test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: [], sortOrder: .newest), .startLoading, @@ -83,12 +77,9 @@ final class AlbumContentViewModelTests: XCTestCase { let expectedNodes = [NodeEntity(name: "sample1.gif", handle: 1)] let useCase = MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities(), albumReloadPublisher: albumReloadPublisher.eraseToAnyPublisher()) - let sut = AlbumContentViewModel(album: albumEntity, - albumContentsUseCase: useCase, - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: albumEntity, + albumContentsUseCase: useCase) + let exp = expectation(description: "show album nodes after update publisher triggered") sut.invokeCommand = { command in switch command { @@ -106,12 +97,9 @@ final class AlbumContentViewModelTests: XCTestCase { } func testIsFavouriteAlbum_isEqualToAlbumEntityType() { - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: NodeEntity(handle: 1), count: 2, type: .favourite), - albumContentsUseCase: MockAlbumContentUseCase(photos: []), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: NodeEntity(handle: 1), count: 2, type: .favourite), + albumContentsUseCase: MockAlbumContentUseCase(photos: [])) + XCTAssertTrue(sut.isFavouriteAlbum) } @@ -119,12 +107,8 @@ final class AlbumContentViewModelTests: XCTestCase { let image = NodeEntity(name: "sample1.gif", handle: 1, mediaType: .image) let video = NodeEntity(name: "sample2.mp4", handle: 2, mediaType: .video) let expectedNodes = [image, video] - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: NodeEntity(handle: 1), count: 2, type: .favourite), - albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: NodeEntity(handle: 1), count: 2, type: .favourite), + albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities())) test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: expectedNodes, sortOrder: .newest)]) let config = try XCTUnwrap(sut.contextMenuConfiguration) @@ -135,77 +119,62 @@ final class AlbumContentViewModelTests: XCTestCase { func testContextMenuConfiguration_onOnlyImagesLoaded_shouldShowImagesAndHideFilter() throws { let images = [NodeEntity(name: "test.jpg", handle: 1)] - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: nil, count: 0, type: .favourite), - albumContentsUseCase: MockAlbumContentUseCase(photos: images.toAlbumPhotoEntities()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: nil, count: 0, type: .favourite), + albumContentsUseCase: MockAlbumContentUseCase(photos: images.toAlbumPhotoEntities())) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: images, sortOrder: .newest)]) let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertNotNil(config.albumType) XCTAssertFalse(config.isFilterEnabled) XCTAssertFalse(config.isEmptyState) } - + func testContextMenuConfiguration_onOnlyVideosLoaded_shouldShowVideosAndHideFilter() throws { let videos = [NodeEntity(name: "test.mp4", handle: 1)] - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: nil, count: 0, type: .favourite), - albumContentsUseCase: MockAlbumContentUseCase(photos: videos.toAlbumPhotoEntities()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "Favourites", coverNode: nil, count: 0, type: .favourite), + albumContentsUseCase: MockAlbumContentUseCase(photos: videos.toAlbumPhotoEntities())) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: videos, sortOrder: .newest)]) let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertNotNil(config.albumType) XCTAssertFalse(config.isFilterEnabled) XCTAssertFalse(config.isEmptyState) } - + func testContextMenuConfiguration_onUserAlbumContentLoadedWithItems_shouldShowFilterAndNotInEmptyState() throws { let image = NodeEntity(name: "sample1.gif", handle: 1, mediaType: .image) let video = NodeEntity(name: "sample2.mp4", handle: 2, mediaType: .video) let expectedNodes = [image, video] - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), - albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), + albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities())) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: [image, video], sortOrder: .newest)]) - + let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertNotNil(config.albumType) XCTAssertTrue(config.isFilterEnabled) XCTAssertFalse(config.isEmptyState) } - + func testContextMenuConfiguration_onRawAlbumContentLoadedWithItems_shouldNotShowFilterAndNotInEmptyState() throws { let expectedNodes = [NodeEntity(name: "sample1.cr2", handle: 1), NodeEntity(name: "sample2.nef", handle: 2)] - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "RAW", coverNode: NodeEntity(handle: 1), count: 2, type: .raw), - albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "RAW", coverNode: NodeEntity(handle: 1), count: 2, type: .raw), + albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities())) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: expectedNodes, sortOrder: .newest)]) let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertNotNil(config.albumType) XCTAssertFalse(config.isFilterEnabled) XCTAssertFalse(config.isEmptyState) } - + func testContextMenuConfiguration_onGifAlbumContentLoadedWithItems_shouldNotShowFilterAndNotInEmptyState() throws { let expectedNodes = [NodeEntity(name: "sample1.gif", handle: 1), NodeEntity(name: "sample2.gif", handle: 2)] - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "Gif", coverNode: NodeEntity(handle: 1), count: 2, type: .gif), - albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "Gif", coverNode: NodeEntity(handle: 1), count: 2, type: .gif), + albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities())) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: expectedNodes, sortOrder: .newest)]) let config = try XCTUnwrap(sut.contextMenuConfiguration) @@ -213,47 +182,40 @@ final class AlbumContentViewModelTests: XCTestCase { XCTAssertFalse(config.isFilterEnabled) XCTAssertFalse(config.isEmptyState) } - + func testContextMenuConfiguration_onImagesOnlyLoadedForUserAlbum_shouldNotEnableFilter() throws { let imageNames: [FileNameEntity] = ["image1.png", "image2.png", "image3.heic"] let expectedImages = imageNames.enumerated().map { (index: Int, name: String) in NodeEntity(name: name, handle: UInt64(index + 1), mediaType: .image) } - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), - albumContentsUseCase: MockAlbumContentUseCase(photos: expectedImages.toAlbumPhotoEntities()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), + albumContentsUseCase: MockAlbumContentUseCase(photos: expectedImages.toAlbumPhotoEntities())) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: expectedImages, sortOrder: .newest)]) - + test(viewModel: sut, action: .changeFilter(.images), expectedCommands: [.showAlbumPhotos(photos: expectedImages, sortOrder: .newest)]) - + let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertNotNil(config.albumType) XCTAssertFalse(config.isFilterEnabled) XCTAssertFalse(config.isEmptyState) } - + func testContextMenuConfiguration_onVideosOnlyLoadedForUserAlbum_shouldNotEnableFilter() throws { let videoNames: [FileNameEntity] = ["video1.mp4", "video2.avi", "video3.mov"] let expectedVideos = videoNames.enumerated().map { (index: Int, name: String) in NodeEntity(name: name, handle: UInt64(index + 1), mediaType: .video) } - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), - albumContentsUseCase: MockAlbumContentUseCase(photos: expectedVideos.toAlbumPhotoEntities()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), + albumContentsUseCase: MockAlbumContentUseCase(photos: expectedVideos.toAlbumPhotoEntities())) test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: expectedVideos, sortOrder: .newest)]) - + test(viewModel: sut, action: .changeFilter(.videos), expectedCommands: [.showAlbumPhotos(photos: expectedVideos, sortOrder: .newest)]) - + let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertNotNil(config.albumType) XCTAssertFalse(config.isFilterEnabled) @@ -261,41 +223,26 @@ final class AlbumContentViewModelTests: XCTestCase { } func testContextMenuConfiguration_onAlbumShareLinkTurnedOff_shouldSetShareLinkStatusToUnavailble() throws { - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, type: .user), - albumContentsUseCase: MockAlbumContentUseCase(), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, - alertViewModel: alertViewModel(), - featureFlagProvider: MockFeatureFlagProvider(list: [.albumShareLink: false])) - + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, type: .user), + featureFlagProvider: MockFeatureFlagProvider(list: [.albumShareLink: false])) + let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertEqual(config.sharedLinkStatus, .unavailable) } func testContextMenuConfiguration_onAlbumSharedLinkTurnedOn_shouldSetCorrectStatusInContext() throws { let expectedAlbumShareLinkStatus = SharedLinkStatusEntity.exported(true) - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, type: .user, sharedLinkStatus: expectedAlbumShareLinkStatus), - albumContentsUseCase: MockAlbumContentUseCase(), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, - alertViewModel: alertViewModel(), - featureFlagProvider: MockFeatureFlagProvider(list: [.albumShareLink: true])) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, type: .user, sharedLinkStatus: expectedAlbumShareLinkStatus), + featureFlagProvider: MockFeatureFlagProvider(list: [.albumShareLink: true])) let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertEqual(config.sharedLinkStatus, expectedAlbumShareLinkStatus) } - + func testDispatchChangeSortOrder_onSortOrderTheSame_shouldDoNothing() throws { - let sut = AlbumContentViewModel(album: albumEntity, - albumContentsUseCase: MockAlbumContentUseCase(photos: []), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: albumEntity, + albumContentsUseCase: MockAlbumContentUseCase(photos: [])) + let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertEqual(config.sortType, .modificationDesc) let exp = expectation(description: "should not call any commands") @@ -307,14 +254,11 @@ final class AlbumContentViewModelTests: XCTestCase { wait(for: [exp], timeout: 1.0) XCTAssertEqual(config.sortType, .modificationDesc) } - + func testDispatchChangeSortOrder_onSortOrderDifferent_shouldShowAlbumWithNewSortedValue() throws { - let sut = AlbumContentViewModel(album: albumEntity, - albumContentsUseCase: MockAlbumContentUseCase(photos: []), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: albumEntity, + albumContentsUseCase: MockAlbumContentUseCase(photos: [])) + let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertEqual(config.sortType, .modificationDesc) let expectedSortOrder = SortOrderType.oldest @@ -323,24 +267,21 @@ final class AlbumContentViewModelTests: XCTestCase { let configAfter = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertEqual(configAfter.sortType, expectedSortOrder.toSortOrderEntity()) } - + func DispatchChangeSortOrder_onSortOrderDifferentWithLoadedContents_shouldShowAlbumWithNewSortedValueAndExistingAlbumContents() throws { let expectedNodes = [NodeEntity(name: "sample1.gif", handle: 1), NodeEntity(name: "sample2.gif", handle: 2)] - let sut = AlbumContentViewModel(album: albumEntity, - albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: albumEntity, + albumContentsUseCase: MockAlbumContentUseCase(photos: expectedNodes.toAlbumPhotoEntities())) + let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertEqual(config.sortType, .modificationDesc) - + let exp = expectation(description: "should show album twice with different sort orders") exp.expectedFulfillmentCount = 2 let expectedSortOrderAfterChange = SortOrderType.oldest var expectedSortOrder = [SortOrderType.newest, expectedSortOrderAfterChange] - + sut.invokeCommand = { command in switch command { case .showAlbumPhotos(let nodes, let sortOrder): @@ -354,19 +295,16 @@ final class AlbumContentViewModelTests: XCTestCase { } sut.dispatch(.onViewReady) sut.dispatch(.changeSortOrder(expectedSortOrderAfterChange)) - + wait(for: [exp], timeout: 1.0) XCTAssertEqual(config.sortType, expectedSortOrderAfterChange.toSortOrderEntity()) XCTAssertTrue(expectedSortOrder.isEmpty) } - + func testDispatchChangeFilter_onFilterTheSame_shouldDoNothing() throws { - let sut = AlbumContentViewModel(album: albumEntity, - albumContentsUseCase: MockAlbumContentUseCase(photos: []), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: albumEntity, + albumContentsUseCase: MockAlbumContentUseCase(photos: [])) + let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertEqual(config.filterType, .allMedia) let exp = expectation(description: "should not call any commands") @@ -378,7 +316,7 @@ final class AlbumContentViewModelTests: XCTestCase { wait(for: [exp], timeout: 1.0) XCTAssertEqual(config.filterType, .allMedia) } - + func testDispatchChangeFilter_onPhotosLoaded_shouldReturnCorrectNodesForFilterTypeAndSetCorrectMenuConfiguration() throws { let imageNames: [FileNameEntity] = ["image1.png", "image2.png", "image3.heic"] let expectedImages = imageNames.enumerated().map { (index: Int, name: String) in @@ -391,30 +329,27 @@ final class AlbumContentViewModelTests: XCTestCase { } let loadedVideos = expectedVideo.toAlbumPhotoEntities() let allMedia = expectedImages + expectedVideo - let sut = AlbumContentViewModel(album: albumEntity, - albumContentsUseCase: MockAlbumContentUseCase(photos: loadedImages + loadedVideos), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: albumEntity, + albumContentsUseCase: MockAlbumContentUseCase(photos: loadedImages + loadedVideos)) + let config = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertEqual(config.filterType, .allMedia) - + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: allMedia, sortOrder: .newest)]) - + test(viewModel: sut, action: .changeFilter(.images), expectedCommands: [.showAlbumPhotos(photos: expectedImages, sortOrder: .newest)], timeout: 0.25) let configAfter = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertEqual(configAfter.filterType, .images) - + test(viewModel: sut, action: .changeFilter(.videos), expectedCommands: [.showAlbumPhotos(photos: expectedVideo, sortOrder: .newest)], timeout: 0.25) let configAfter1 = try XCTUnwrap(sut.contextMenuConfiguration) XCTAssertEqual(configAfter1.filterType, .videos) - + test(viewModel: sut, action: .changeFilter(.allMedia), expectedCommands: [.showAlbumPhotos(photos: allMedia, sortOrder: .newest)], timeout: 0.25) @@ -423,12 +358,9 @@ final class AlbumContentViewModelTests: XCTestCase { } func testShouldShowAddToAlbumButton_onPhotoLibraryNotEmptyOnUserAlbum_shouldReturnTrue() { - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), - albumContentsUseCase: MockAlbumContentUseCase(photos: []), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(allPhotos: [NodeEntity(name: "photo 1.jpg", handle: 1)]), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), + albumContentsUseCase: MockAlbumContentUseCase(photos: []), + photoLibraryUseCase: MockPhotoLibraryUseCase(allPhotos: [NodeEntity(name: "photo 1.jpg", handle: 1)])) test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: [], sortOrder: .newest)]) @@ -436,12 +368,8 @@ final class AlbumContentViewModelTests: XCTestCase { } func testShouldShowAddToAlbumButton_onPhotoLibraryEmptyOnUserAlbum_shouldReturnFalse() { - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), - albumContentsUseCase: MockAlbumContentUseCase(photos: []), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), + albumContentsUseCase: MockAlbumContentUseCase(photos: [])) test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: [], sortOrder: .newest)]) @@ -449,12 +377,11 @@ final class AlbumContentViewModelTests: XCTestCase { } func testOnDispatchAddItemsToAlbum_routeToShowAlbumContentPicker() { - let sut = AlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), - albumContentsUseCase: MockAlbumContentUseCase(photos: []), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let router = MockAlbumContentRouting() + let sut = makeAlbumContentViewModel(album: AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user), + albumContentsUseCase: MockAlbumContentUseCase(photos: []), + router: router) + sut.showAlbumContentPicker() XCTAssertEqual(router.showAlbumContentPickerCalled, 1) } @@ -465,12 +392,10 @@ final class AlbumContentViewModelTests: XCTestCase { let albumContentRouter = MockAlbumContentRouting(album: album, photos: expectedAddedPhotos) let result = AlbumElementsResultEntity(success: UInt(expectedAddedPhotos.count), failure: 0) let albumModificationUseCase = MockAlbumModificationUseCase(addPhotosResult: .success(result)) - let sut = AlbumContentViewModel(album: album, - albumContentsUseCase: MockAlbumContentUseCase(photos: expectedAddedPhotos.toAlbumPhotoEntities()), - albumModificationUseCase: albumModificationUseCase, - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: albumContentRouter, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: album, + albumContentsUseCase: MockAlbumContentUseCase(photos: expectedAddedPhotos.toAlbumPhotoEntities()), + albumModificationUseCase: albumModificationUseCase, + router: albumContentRouter) let exp = expectation(description: "Should show completion message after items added") exp.expectedFulfillmentCount = 3 @@ -503,12 +428,10 @@ final class AlbumContentViewModelTests: XCTestCase { let album = AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user) let albumContentRouter = MockAlbumContentRouting(album: album, photos: expectedAddedPhotos) let albumModificationUseCase = MockAlbumModificationUseCase() - let sut = AlbumContentViewModel(album: album, - albumContentsUseCase: MockAlbumContentUseCase(photos: expectedAddedPhotos.toAlbumPhotoEntities()), - albumModificationUseCase: albumModificationUseCase, - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: albumContentRouter, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: album, + albumContentsUseCase: MockAlbumContentUseCase(photos: expectedAddedPhotos.toAlbumPhotoEntities()), + albumModificationUseCase: albumModificationUseCase, + router: albumContentRouter) test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: expectedAddedPhotos, sortOrder: .newest)]) @@ -534,12 +457,10 @@ final class AlbumContentViewModelTests: XCTestCase { let album = AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user) let albumContentRouter = MockAlbumContentRouting(album: album, photos: expectedAddedPhotos) let albumModificationUseCase = MockAlbumModificationUseCase(addPhotosResult: .failure(AlbumErrorEntity.generic)) - let sut = AlbumContentViewModel(album: album, - albumContentsUseCase: MockAlbumContentUseCase(photos: []), - albumModificationUseCase: albumModificationUseCase, - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: albumContentRouter, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: album, + albumContentsUseCase: MockAlbumContentUseCase(photos: []), + albumModificationUseCase: albumModificationUseCase, + router: albumContentRouter) test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: [], sortOrder: .newest)]) @@ -565,14 +486,10 @@ final class AlbumContentViewModelTests: XCTestCase { func testRenameAlbum_whenUserRenameAlbum_shouldUpdateAlbumNameAndNavigationTitle() { let photo = [NodeEntity(name: "a.jpg", handle: 1)] let album = AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user) - let albumContentRouter = MockAlbumContentRouting(album: album, photos: photo) let albumModificationUseCase = MockAlbumModificationUseCase() - let sut = AlbumContentViewModel(album: album, - albumContentsUseCase: MockAlbumContentUseCase(photos: photo.toAlbumPhotoEntities()), - albumModificationUseCase: albumModificationUseCase, - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: albumContentRouter, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: album, + albumContentsUseCase: MockAlbumContentUseCase(photos: photo.toAlbumPhotoEntities()), + albumModificationUseCase: albumModificationUseCase) let exp = expectation(description: "Should update navigation title") sut.invokeCommand = { @@ -599,13 +516,11 @@ final class AlbumContentViewModelTests: XCTestCase { let alertViewModel = TextFieldAlertViewModel(textString: "Old Album", title: "Hey there", placeholderText: "", affirmativeButtonTitle: "Rename", affirmativeButtonInitiallyEnabled: true, message: "", action: nil, validator: nil) - let sut = AlbumContentViewModel(album: album, - albumContentsUseCase: MockAlbumContentUseCase(photos: photo.toAlbumPhotoEntities()), - albumModificationUseCase: albumModificationUseCase, - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: albumContentRouter, - alertViewModel: alertViewModel) + let sut = makeAlbumContentViewModel(album: album, + albumContentsUseCase: MockAlbumContentUseCase(photos: photo.toAlbumPhotoEntities()), + albumModificationUseCase: albumModificationUseCase, + router: albumContentRouter, + alertViewModel: alertViewModel) let expectedName = "New Album" let exp = expectation(description: "Should update navigation title") @@ -630,12 +545,11 @@ final class AlbumContentViewModelTests: XCTestCase { let albumContentRouter = MockAlbumContentRouting(album: album, albumPhoto: AlbumPhotoEntity(photo: NodeEntity(handle: HandleEntity(1))), photos: photos) let albumModificationUseCase = MockAlbumModificationUseCase() - let sut = AlbumContentViewModel(album: album, - albumContentsUseCase: MockAlbumContentUseCase(photos: photos.map {AlbumPhotoEntity(photo: $0)}), - albumModificationUseCase: albumModificationUseCase, - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: albumContentRouter, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: album, + albumContentsUseCase: MockAlbumContentUseCase(photos: photos.map {AlbumPhotoEntity(photo: $0)}), + albumModificationUseCase: albumModificationUseCase, + router: albumContentRouter) + test(viewModel: sut, action: .showAlbumCoverPicker, expectedCommands: [.showResultMessage(.success(Strings.Localizable.CameraUploads.Albums.albumCoverUpdated))]) } @@ -646,13 +560,11 @@ final class AlbumContentViewModelTests: XCTestCase { let albumPhotos = nodesToRemove.enumerated().map { AlbumPhotoEntity(photo: $0.element, albumPhotoId: UInt64($0.offset + 1))} let resultEntity = AlbumElementsResultEntity(success: UInt(nodesToRemove.count), failure: 0) let albumModificationUseCase = MockAlbumModificationUseCase(resultEntity: resultEntity) - - let sut = AlbumContentViewModel(album: album, - albumContentsUseCase: MockAlbumContentUseCase(photos: albumPhotos), - albumModificationUseCase: albumModificationUseCase, - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + + let sut = makeAlbumContentViewModel(album: album, + albumContentsUseCase: MockAlbumContentUseCase(photos: albumPhotos), + albumModificationUseCase: albumModificationUseCase) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: nodesToRemove, sortOrder: .newest)]) @@ -667,12 +579,10 @@ final class AlbumContentViewModelTests: XCTestCase { let album = AlbumEntity(id: 1, name: "User Album", coverNode: NodeEntity(handle: 1), count: 2, type: .user) let nodesToRemove = [NodeEntity(handle: 1), NodeEntity(handle: 2)] let albumModificationUseCase = MockAlbumModificationUseCase() - let sut = AlbumContentViewModel(album: album, - albumContentsUseCase: MockAlbumContentUseCase(photos: []), - albumModificationUseCase: albumModificationUseCase, - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: album, + albumContentsUseCase: MockAlbumContentUseCase(photos: []), + albumModificationUseCase: albumModificationUseCase) + test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: [], sortOrder: .newest)]) let exp = expectation(description: "Should not invoke any commands") @@ -693,12 +603,8 @@ final class AlbumContentViewModelTests: XCTestCase { let albumReloadPublisher = PassthroughSubject() var albumContentsUseCase = MockAlbumContentUseCase(photos: allPhotos.toAlbumPhotoEntities(), albumReloadPublisher: albumReloadPublisher.eraseToAnyPublisher()) - let sut = AlbumContentViewModel(album: album, - albumContentsUseCase: albumContentsUseCase, - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: album, + albumContentsUseCase: albumContentsUseCase) test(viewModel: sut, action: .onViewReady, expectedCommands: [.showAlbumPhotos(photos: allPhotos, sortOrder: .newest)]) @@ -726,13 +632,8 @@ final class AlbumContentViewModelTests: XCTestCase { func testDispatchDeleteAlbum_onSuccessfulRemovalOfAlbum_shouldShowHudOfRemoveAlbum() { let album = AlbumEntity(id: 1, name: "User Album", coverNode: nil, count: 1, type: .user) let albumModificationUseCase = MockAlbumModificationUseCase(albums: [album]) - - let sut = AlbumContentViewModel(album: album, - albumContentsUseCase: MockAlbumContentUseCase(), - albumModificationUseCase: albumModificationUseCase, - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: album, + albumModificationUseCase: albumModificationUseCase) let message = Strings.Localizable.CameraUploads.Albums.deleteAlbumSuccess(1) .replacingOccurrences(of: "[A]", with: album.name) @@ -746,13 +647,8 @@ final class AlbumContentViewModelTests: XCTestCase { } func testDispatchConfigureContextMenu_onReceived_shouldRebuildContextMenuWithNewSelectHiddenValue() { - let sut = AlbumContentViewModel(album: albumEntity, - albumContentsUseCase: MockAlbumContentUseCase(), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, - alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: albumEntity) + let expectedContextConfigurationSelectHidden = true test(viewModel: sut, action: .configureContextMenu(isSelectHidden: expectedContextConfigurationSelectHidden), expectedCommands: [.rebuildContextMenu]) @@ -762,13 +658,9 @@ final class AlbumContentViewModelTests: XCTestCase { func testSubscription_onUserAlbumPublisherEmission_shouldDismissIfSetContainsRemoveChangeType() { let userAlbum = AlbumEntity(id: 1, type: .user) let albumUpdatedPublisher = PassthroughSubject() - let sut = AlbumContentViewModel(album: userAlbum, - albumContentsUseCase: MockAlbumContentUseCase(albumUpdatedPublisher: albumUpdatedPublisher.eraseToAnyPublisher()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, - alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: userAlbum, + albumContentsUseCase: MockAlbumContentUseCase(albumUpdatedPublisher: albumUpdatedPublisher.eraseToAnyPublisher())) + let exp = expectation(description: "album dismissal") sut.invokeCommand = { switch $0 { @@ -785,13 +677,9 @@ final class AlbumContentViewModelTests: XCTestCase { func testSubscription_onUserAlbumPublisherEmission_shouldUpdateNavigationTitleNameIfItContainsNameChangeType() { let userAlbum = AlbumEntity(id: 1, type: .user) let albumUpdatedPublisher = PassthroughSubject() - let sut = AlbumContentViewModel(album: userAlbum, - albumContentsUseCase: MockAlbumContentUseCase(albumUpdatedPublisher: albumUpdatedPublisher.eraseToAnyPublisher()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, - alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: userAlbum, + albumContentsUseCase: MockAlbumContentUseCase(albumUpdatedPublisher: albumUpdatedPublisher.eraseToAnyPublisher())) + let expectedNewName = "The new name" let exp = expectation(description: "album name should update") sut.invokeCommand = { @@ -807,28 +695,28 @@ final class AlbumContentViewModelTests: XCTestCase { wait(for: [exp], timeout: 1.0) } - func testDispatch_onShareLink_shouldCallRouterToShareLink() { + func testDispatch_onShareLink_shouldCallRouterToShareLinkAndTrackAnalyticsEvent() { let userAlbum = AlbumEntity(id: 1, type: .user) - let sut = AlbumContentViewModel(album: userAlbum, - albumContentsUseCase: MockAlbumContentUseCase(), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, - alertViewModel: alertViewModel()) + let router = MockAlbumContentRouting() + let tracker = MockTracker() + let sut = makeAlbumContentViewModel(album: userAlbum, + router: router, + tracker: tracker) + sut.dispatch(.shareLink) XCTAssertEqual(router.showShareLinkCalled, 1) + + tracker.assertTrackAnalyticsEventCalled( + with: [ + AlbumContentShareLinkMenuToolbarEvent() + ] + ) } func testAction_removeLink_shouldShowSuccessAfterRemoved() { let userAlbum = AlbumEntity(id: 1, type: .user) - let sut = AlbumContentViewModel(album: userAlbum, - albumContentsUseCase: MockAlbumContentUseCase(), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(removeSharedAlbumLinkResult: .success), - router: router, - alertViewModel: alertViewModel()) + let sut = makeAlbumContentViewModel(album: userAlbum, + shareAlbumUseCase: MockShareAlbumUseCase(removeSharedAlbumLinkResult: .success)) test(viewModel: sut, action: .removeLink, expectedCommands: [ .showResultMessage(.success(Strings.Localizable.CameraUploads.Albums.removeShareLinkSuccessMessage(1))) @@ -838,14 +726,9 @@ final class AlbumContentViewModelTests: XCTestCase { func testSubscription_onUserAlbumPublisherEmission_shouldUpdateContextMenuIfAlbumContainsExportedChangeType() throws { let userAlbum = AlbumEntity(id: 1, type: .user) let albumUpdatedPublisher = PassthroughSubject() - let sut = AlbumContentViewModel(album: userAlbum, - albumContentsUseCase: MockAlbumContentUseCase(albumUpdatedPublisher: albumUpdatedPublisher.eraseToAnyPublisher()), - albumModificationUseCase: MockAlbumModificationUseCase(), - photoLibraryUseCase: MockPhotoLibraryUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase(), - router: router, - alertViewModel: alertViewModel(), - featureFlagProvider: MockFeatureFlagProvider(list: [.albumShareLink: true])) + let sut = makeAlbumContentViewModel(album: userAlbum, + albumContentsUseCase: MockAlbumContentUseCase(albumUpdatedPublisher: albumUpdatedPublisher.eraseToAnyPublisher()), + featureFlagProvider: MockFeatureFlagProvider(list: [.albumShareLink: true])) let exp = expectation(description: "context menu should rebuild") sut.invokeCommand = { @@ -864,8 +747,36 @@ final class AlbumContentViewModelTests: XCTestCase { XCTAssertEqual(config.sharedLinkStatus, .exported(isExported)) } - private func alertViewModel() -> TextFieldAlertViewModel { - TextFieldAlertViewModel(title: Strings.Localizable.CameraUploads.Albums.Create.Alert.title, placeholderText: Strings.Localizable.CameraUploads.Albums.Create.Alert.placeholder, affirmativeButtonTitle: Strings.Localizable.rename, message: nil) + // MARK: - Helpers + + private func makeAlbumContentViewModel( + album: AlbumEntity, + albumContentsUseCase: some AlbumContentsUseCaseProtocol = MockAlbumContentUseCase(), + albumModificationUseCase: some AlbumModificationUseCaseProtocol = MockAlbumModificationUseCase(), + photoLibraryUseCase: some PhotoLibraryUseCaseProtocol = MockPhotoLibraryUseCase(), + shareAlbumUseCase: some ShareAlbumUseCaseProtocol = MockShareAlbumUseCase(), + router: some AlbumContentRouting = MockAlbumContentRouting(), + newAlbumPhotosToAdd: [NodeEntity]? = nil, + alertViewModel: TextFieldAlertViewModel? = nil, + featureFlagProvider: some FeatureFlagProviderProtocol = MockFeatureFlagProvider(list: [:]), + tracker: some AnalyticsTracking = MockTracker() + ) -> AlbumContentViewModel { + AlbumContentViewModel(album: album, + albumContentsUseCase: albumContentsUseCase, + albumModificationUseCase: albumModificationUseCase, + photoLibraryUseCase: photoLibraryUseCase, + shareAlbumUseCase: shareAlbumUseCase, + router: router, + newAlbumPhotosToAdd: newAlbumPhotosToAdd, + alertViewModel: alertViewModel ?? makeAlertViewModel(), + featureFlagProvider: featureFlagProvider, + tracker: tracker) + } + + private func makeAlertViewModel() -> TextFieldAlertViewModel { + TextFieldAlertViewModel(title: Strings.Localizable.CameraUploads.Albums.Create.Alert.title, + placeholderText: Strings.Localizable.CameraUploads.Albums.Create.Alert.placeholder, + affirmativeButtonTitle: Strings.Localizable.rename, message: nil) } } diff --git a/MEGAUnitTests/Album/AlbumListViewModelTests.swift b/MEGAUnitTests/Album/AlbumListViewModelTests.swift index 6638886b60..d374fa8c20 100644 --- a/MEGAUnitTests/Album/AlbumListViewModelTests.swift +++ b/MEGAUnitTests/Album/AlbumListViewModelTests.swift @@ -1,7 +1,10 @@ import Combine @testable import MEGA +import MEGAAnalyticsiOS import MEGADomain import MEGADomainMock +import MEGAL10n +import MEGAPresentation import XCTest @MainActor @@ -9,9 +12,9 @@ final class AlbumListViewModelTests: XCTestCase { private var subscriptions = Set() func testLoadAlbums_onAlbumsLoaded_systemAlbumsTitlesAreUpdatedAndAlbumsAreSortedCorrectly() async throws { - let favouriteAlbum = AlbumEntity(id: 1, name: "", coverNode: NodeEntity(handle: 1), count: 1, type: .favourite) - let gifAlbum = AlbumEntity(id: 2, name: "", coverNode: NodeEntity(handle: 1), count: 1, type: .gif) - let rawAlbum = AlbumEntity(id: 3, name: "", coverNode: NodeEntity(handle: 2), count: 1, type: .raw) + var favouriteAlbum = AlbumEntity(id: 1, name: "", coverNode: NodeEntity(handle: 1), count: 1, type: .favourite) + var gifAlbum = AlbumEntity(id: 2, name: "", coverNode: NodeEntity(handle: 1), count: 1, type: .gif) + var rawAlbum = AlbumEntity(id: 3, name: "", coverNode: NodeEntity(handle: 2), count: 1, type: .raw) let userAlbum1 = AlbumEntity(id: 4, name: "Album 1", coverNode: NodeEntity(handle: 3), count: 1, type: .user, creationTime: try "2022-12-31T22:01:04Z".date) let userAlbum2 = AlbumEntity(id: 5, name: "Album 2", coverNode: NodeEntity(handle: 4), @@ -24,15 +27,19 @@ final class AlbumListViewModelTests: XCTestCase { count: 1, type: .user, creationTime: try "2022-12-31T22:05:04Z".date) let useCase = MockAlbumListUseCase(albums: [favouriteAlbum, gifAlbum, rawAlbum, userAlbum1, userAlbum2, userAlbum3, userAlbum4, userAlbum5]) - + // Update titles to expected + favouriteAlbum.name = Strings.Localizable.CameraUploads.Albums.Favourites.title + gifAlbum.name = Strings.Localizable.CameraUploads.Albums.Gif.title + rawAlbum.name = Strings.Localizable.CameraUploads.Albums.Raw.title let sut = albumListViewModel(usecase: useCase) sut.loadAlbums() await sut.albumLoadingTask?.value + XCTAssertEqual(sut.albums, [ - favouriteAlbum.update(name: Strings.Localizable.CameraUploads.Albums.Favourites.title), - gifAlbum.update(name: Strings.Localizable.CameraUploads.Albums.Gif.title), - rawAlbum.update(name: Strings.Localizable.CameraUploads.Albums.Raw.title), + favouriteAlbum, + gifAlbum, + rawAlbum, userAlbum5, userAlbum4, userAlbum3, @@ -41,7 +48,7 @@ final class AlbumListViewModelTests: XCTestCase { ]) } - func testLoadAlbums_onAlbumsLoadedFinsihed_shouldLoadSetToFalse() async throws { + func testLoadAlbums_onAlbumsLoadedFinished_shouldLoadSetToFalse() async throws { let sut = albumListViewModel() let exp = expectation(description: "should load set after album load") @@ -287,12 +294,37 @@ final class AlbumListViewModelTests: XCTestCase { } func testOnAlbumTap_whenUserTap_shouldSetCorrectValues() { - let sut = albumListViewModel() + let tracker = MockTracker() + let sut = albumListViewModel(tracker: tracker) + let gifAlbum = AlbumEntity(id: 2, name: "", coverNode: NodeEntity(handle: 1), count: 1, type: .gif) sut.onAlbumTap(gifAlbum) XCTAssertNil(sut.albumCreationAlertMsg) XCTAssertEqual(sut.album, gifAlbum) + + tracker.assertTrackAnalyticsEventCalled( + with: [ + gifAlbum.makeAlbumSelectedEvent(selectionType: .single) + ] + ) + } + + func testOnAlbumTap_notInEditMode_shouldSendSelectedEvent() { + let userAlbum = AlbumEntity(id: 5, type: .user, + metaData: AlbumMetaDataEntity( + imageCount: 6, + videoCount: 8)) + let tracker = MockTracker() + let sut = albumListViewModel(tracker: tracker) + + sut.onAlbumTap(userAlbum) + + tracker.assertTrackAnalyticsEventCalled( + with: [ + userAlbum.makeAlbumSelectedEvent(selectionType: .single) + ] + ) } func testOnCreateAlbum_whenIsEditModeActive_shouldReturnFalseForShowCreateAlbumAlert() { @@ -324,7 +356,7 @@ final class AlbumListViewModelTests: XCTestCase { XCTAssertEqual(sut.albumNames.sorted(), ["Hey there", "", "Favourites"].sorted()) } - func testReloadUpdates_onAlbumsUpdateEmiited_shouldRealodAlbums() { + func testReloadUpdates_onAlbumsUpdateEmitted_shouldReloadAlbums() { let albums = [AlbumEntity(id: 4, name: "Album 1", coverNode: NodeEntity(handle: 3), count: 1, type: .user)] let albumsUpdatedPublisher = PassthroughSubject() @@ -509,7 +541,9 @@ final class AlbumListViewModelTests: XCTestCase { let photoAlbumContainerViewModel = PhotoAlbumContainerViewModel() let sut = albumListViewModel(photoAlbumContainerViewModel: photoAlbumContainerViewModel) XCTAssertFalse(sut.showShareAlbumLinks) + photoAlbumContainerViewModel.showShareAlbumLinks = true + XCTAssertTrue(sut.showShareAlbumLinks) } @@ -549,7 +583,7 @@ final class AlbumListViewModelTests: XCTestCase { XCTAssertNotNil(sut.albumLoadingTask, "Expect to initialize albumLoadingTask, but not initialized instead.") } - func testOnViewDissappeared_whenCalled_releasesAlbumsUpdatedSubscription() { + func testOnViewDisappeared_whenCalled_releasesAlbumsUpdatedSubscription() { let sut = albumListViewModel() var isViewVisible = false let exp = expectation(description: "Wait for subscription") @@ -560,14 +594,14 @@ final class AlbumListViewModelTests: XCTestCase { } .store(in: &subscriptions) - sut.onViewDissappeared() + sut.onViewDisappeared() wait(for: [exp], timeout: 0.1) XCTAssertFalse(isViewVisible, "Expect view is not visible.") XCTAssertNil(sut.albumLoadingTask, "Expect to deallocate albumLoadingTask, but initialized instead.") } - func testOnViewAppearedDissappeared_whenCalled_subscribeAndCancelLoadAlbumTasks() async throws { + func testOnViewAppearedDisappeared_whenCalled_subscribeAndCancelLoadAlbumTasks() async throws { let sut = albumListViewModel() var isViewVisible = false let exp = expectation(description: "Wait for subscription emitted value") @@ -585,7 +619,7 @@ final class AlbumListViewModelTests: XCTestCase { XCTAssertTrue(isViewVisible, "Expect view is visible.") XCTAssertNotNil(sut.albumLoadingTask, "Expect to initialize albumLoadingTask, but not initialized instead.") - sut.onViewDissappeared() + sut.onViewDisappeared() await sut.albumLoadingTask?.value await fulfillment(of: [exp], timeout: 0.1) @@ -604,15 +638,17 @@ final class AlbumListViewModelTests: XCTestCase { } private func albumListViewModel( - usecase: MockAlbumListUseCase = MockAlbumListUseCase(), - albumModificationUseCase: MockAlbumModificationUseCase = MockAlbumModificationUseCase(), - shareAlbumUseCase: MockShareAlbumUseCase = MockShareAlbumUseCase(), + usecase: some AlbumListUseCaseProtocol = MockAlbumListUseCase(), + albumModificationUseCase: some AlbumModificationUseCaseProtocol = MockAlbumModificationUseCase(), + shareAlbumUseCase: some ShareAlbumUseCaseProtocol = MockShareAlbumUseCase(), + tracker: some AnalyticsTracking = MockTracker(), photoAlbumContainerViewModel: PhotoAlbumContainerViewModel? = nil ) -> AlbumListViewModel { AlbumListViewModel( usecase: usecase, albumModificationUseCase: albumModificationUseCase, shareAlbumUseCase: shareAlbumUseCase, + tracker: tracker, alertViewModel: alertViewModel(), photoAlbumContainerViewModel: photoAlbumContainerViewModel ) diff --git a/MEGAUnitTests/Album/AlbumNameValidatorTests.swift b/MEGAUnitTests/Album/AlbumNameValidatorTests.swift index 052a292bd4..5dd8b7656f 100644 --- a/MEGAUnitTests/Album/AlbumNameValidatorTests.swift +++ b/MEGAUnitTests/Album/AlbumNameValidatorTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class AlbumNameValidatorTests: XCTestCase { diff --git a/MEGAUnitTests/Album/Links/ImportAlbumViewModelTests.swift b/MEGAUnitTests/Album/Links/ImportAlbumViewModelTests.swift index 0571f91a97..3b67171c3b 100644 --- a/MEGAUnitTests/Album/Links/ImportAlbumViewModelTests.swift +++ b/MEGAUnitTests/Album/Links/ImportAlbumViewModelTests.swift @@ -3,6 +3,9 @@ import Combine import MEGAAnalyticsiOS import MEGADomain import MEGADomainMock +import MEGAL10n +import MEGAPermissions +import MEGAPermissionsMock import MEGAPresentation import MEGATest import XCTest @@ -176,23 +179,21 @@ final class ImportAlbumViewModelTests: XCTestCase { XCTAssertEqual(sut.selectionNavigationTitle, Strings.Localizable.selectTitle) sut.photoLibraryContentViewModel.selection.setSelectedPhotos([NodeEntity(handle: 5)]) - XCTAssertEqual(sut.selectionNavigationTitle, Strings.Localizable.oneItemSelected(1)) + XCTAssertEqual(sut.selectionNavigationTitle, Strings.Localizable.General.Format.itemsSelected(1)) let multiplePhotos = try makePhotos() sut.photoLibraryContentViewModel.selection.setSelectedPhotos(multiplePhotos) - XCTAssertEqual(sut.selectionNavigationTitle, Strings.Localizable.itemsSelected(multiplePhotos.count)) + XCTAssertEqual(sut.selectionNavigationTitle, Strings.Localizable.General.Format.itemsSelected(multiplePhotos.count)) sut.photoLibraryContentViewModel.selection.allSelected = false XCTAssertEqual(sut.selectionNavigationTitle, Strings.Localizable.selectTitle) } func testIsToolbarButtonsDisabled_photosLoadedAndSelection_shouldEnableAndDisableCorrectly() async throws { - let photos = try makePhotos() - let sharedAlbumEntity = makeSharedAlbumEntity(set: SetEntity(handle: 2)) - let albumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(sharedAlbumEntity), - nodes: photos) + let publicAlbumUseCase = makePublicAlbumUseCase(nodes: try makePhotos()) + let sut = makeImportAlbumViewModel(publicLink: try requireDecryptionKeyAlbumLink, - publicAlbumUseCase: albumUseCase) + publicAlbumUseCase: publicAlbumUseCase) XCTAssertTrue(sut.isToolbarButtonsDisabled) sut.publicLinkDecryptionKey = "Nt8-bopPB8em4cOlKas" @@ -213,10 +214,10 @@ final class ImportAlbumViewModelTests: XCTestCase { } func testIsToolbarButtonDisabled_noPhotosLoaded_shouldDisable() async throws { - let sharedAlbumEntity = makeSharedAlbumEntity(set: SetEntity(handle: 2)) - let albumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(sharedAlbumEntity)) + let publicAlbumUseCase = makePublicAlbumUseCase() + let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, - publicAlbumUseCase: albumUseCase) + publicAlbumUseCase: publicAlbumUseCase) XCTAssertTrue(sut.isToolbarButtonsDisabled) await sut.loadPublicAlbum() @@ -225,10 +226,8 @@ final class ImportAlbumViewModelTests: XCTestCase { } func testSelectButtonOpacity_onPhotosLoadedAndSelectionHiddenChange_shouldChangeCorrectly() async throws { - let sharedAlbumEntity = makeSharedAlbumEntity(set: SetEntity(handle: 2)) - let publicAlbumUseCase = MockPublicAlbumUseCase( - publicAlbumResult: .success(sharedAlbumEntity), - nodes: try makePhotos()) + let publicAlbumUseCase = makePublicAlbumUseCase(nodes: try makePhotos()) + let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, publicAlbumUseCase: publicAlbumUseCase) XCTAssertEqual(sut.selectButtonOpacity, 0.3, accuracy: 0.1) @@ -258,9 +257,9 @@ final class ImportAlbumViewModelTests: XCTestCase { } func testImportAlbum_onAlbumNameNotInConflict_shouldShowImportLocation() async throws { - let album = SetEntity(handle: 3, name: "valid album name") - let sharedAlbumEntity = makeSharedAlbumEntity(set: album) - let publicAlbumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(sharedAlbumEntity)) + let publicAlbumUseCase = makePublicAlbumUseCase( + handle: 3, + name: "valid album name") let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, publicAlbumUseCase: publicAlbumUseCase) await sut.loadPublicAlbum() @@ -271,10 +270,9 @@ final class ImportAlbumViewModelTests: XCTestCase { } func testImportAlbum_onAlbumNameInConflict_shouldShowRenameAlbumAlert() async throws { - let album = SetEntity(handle: 3, - name: Strings.Localizable.CameraUploads.Albums.Favourites.title) - let sharedAlbumEntity = makeSharedAlbumEntity(set: album) - let publicAlbumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(sharedAlbumEntity)) + let publicAlbumUseCase = makePublicAlbumUseCase( + handle: 3, + name: Strings.Localizable.CameraUploads.Albums.Favourites.title) let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, publicAlbumUseCase: publicAlbumUseCase) await sut.loadPublicAlbum() @@ -286,10 +284,10 @@ final class ImportAlbumViewModelTests: XCTestCase { func testImportAlbum_onAccountStorageWillExceed_shouldShowStorageAlert() async throws { // Arrange - let album = SetEntity(handle: 3, - name: Strings.Localizable.CameraUploads.Albums.Favourites.title) - let sharedAlbumEntity = makeSharedAlbumEntity(set: album) - let publicAlbumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(sharedAlbumEntity)) + let publicAlbumUseCase = makePublicAlbumUseCase( + handle: 3, + name: Strings.Localizable.CameraUploads.Albums.Favourites.title) + let accountStorageUseCase = MockAccountStorageUseCase(willStorageQuotaExceed: true) let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, @@ -304,10 +302,10 @@ final class ImportAlbumViewModelTests: XCTestCase { func testImportAlbum_onAccountStorageWillNotExceed_shouldNotShowStorageAlert() async throws { // Arrange - let album = SetEntity(handle: 3, - name: Strings.Localizable.CameraUploads.Albums.Favourites.title) - let sharedAlbumEntity = makeSharedAlbumEntity(set: album) - let publicAlbumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(sharedAlbumEntity)) + let publicAlbumUseCase = makePublicAlbumUseCase( + handle: 3, + name: Strings.Localizable.CameraUploads.Albums.Favourites.title) + let accountStorageUseCase = MockAccountStorageUseCase(willStorageQuotaExceed: false) let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, @@ -323,11 +321,22 @@ final class ImportAlbumViewModelTests: XCTestCase { XCTAssertFalse(sut.showStorageQuotaWillExceed) } + func testImportAlbum_internetNotConnected_shouldToggleShowNoInternetConnection() async throws { + let monitorUseCase = MockNetworkMonitorUseCase(connected: false) + let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, + monitorUseCase: monitorUseCase) + await sut.loadPublicAlbum() + XCTAssertFalse(sut.showNoInternetConnection) + + await sut.importAlbum() + + XCTAssertTrue(sut.showNoInternetConnection) + } + func testImportFolderLocation_onFolderSelected_shouldImportAlbumPhotosAndShowSnackbar() async throws { let albumName = "New Album (1)" - let album = makeSharedAlbumEntity(set: SetEntity(handle: 24, name: albumName)) - let publicAlbumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(album), - nodes: try makePhotos()) + let publicAlbumUseCase = makePublicAlbumUseCase(handle: 24, name: albumName, nodes: try makePhotos()) + let importPublicAlbumUseCase = MockImportPublicAlbumUseCase(importAlbumResult: .success) let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, publicAlbumUseCase: publicAlbumUseCase, @@ -350,8 +359,7 @@ final class ImportAlbumViewModelTests: XCTestCase { await sut.importAlbumTask?.value await fulfillment(of: [exp], timeout: 1.0) - XCTAssertTrue(sut.showSnackBar) - XCTAssertEqual(sut.snackBarViewModel().snackBar, + XCTAssertEqual(sut.snackBarViewModel?.snackBar, SnackBar(message: Strings.Localizable.AlbumLink.Alert.Message.albumSavedToCloudDrive(albumName))) } @@ -371,8 +379,7 @@ final class ImportAlbumViewModelTests: XCTestCase { sut.importFolderLocation = NodeEntity(handle: 64, isFolder: true) await sut.importAlbumTask?.value - XCTAssertTrue(sut.showSnackBar) - XCTAssertEqual(sut.snackBarViewModel().snackBar, + XCTAssertEqual(sut.snackBarViewModel?.snackBar, SnackBar(message: Strings.Localizable.AlbumLink.Alert.Message.albumFailedToSaveToCloudDrive(albumName))) } @@ -398,9 +405,7 @@ final class ImportAlbumViewModelTests: XCTestCase { NodeEntity(handle: 76)] let importPublicAlbumUseCase = MockImportPublicAlbumUseCase( importAlbumResult: .success) - let album = makeSharedAlbumEntity(set: SetEntity(handle: 24, name: "Test")) - let publicAlbumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(album), - nodes: try makePhotos()) + let publicAlbumUseCase = makePublicAlbumUseCase(handle: 24, name: "Test", nodes: try makePhotos()) let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, publicAlbumUseCase: publicAlbumUseCase, importPublicAlbumUseCase: importPublicAlbumUseCase) @@ -412,13 +417,23 @@ final class ImportAlbumViewModelTests: XCTestCase { sut.importFolderLocation = NodeEntity(handle: 64, isFolder: true) await sut.importAlbumTask?.value - XCTAssertTrue(sut.showSnackBar) XCTAssertEqual(Set(importPublicAlbumUseCase.photosToImport ?? []), Set(selectedPhotos)) - XCTAssertEqual(sut.snackBarViewModel().snackBar, + XCTAssertEqual(sut.snackBarViewModel?.snackBar, SnackBar(message: Strings.Localizable.AlbumLink.Alert.Message.filesSaveToCloudDrive(selectedPhotos.count))) } + func testImportFolderLocation_noInternetConnection_shouldToggleShowNoInternetConnection() throws { + let monitorUseCase = MockNetworkMonitorUseCase(connected: false) + let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, + monitorUseCase: monitorUseCase) + XCTAssertFalse(sut.showNoInternetConnection) + + sut.importFolderLocation = NodeEntity(handle: 24, isFolder: true) + + XCTAssertTrue(sut.showNoInternetConnection) + } + func testShowImportToolbarButton_userNotLoggedIn_shouldNotShowImportBarButtonAndToggleWithSelection() throws { let accountUseCase = MockAccountUseCase(isLoggedIn: false) let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, @@ -432,9 +447,7 @@ final class ImportAlbumViewModelTests: XCTestCase { func testRenameAlbum_newNameProvided_shouldShowImportAlbumLocationAndUseNewNameDuringImport() async throws { let newAlbumName = "The new album name" - let album = makeSharedAlbumEntity(set: SetEntity(handle: 24, name: "Test")) - let publicAlbumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(album), - nodes: try makePhotos()) + let publicAlbumUseCase = makePublicAlbumUseCase(handle: 24, name: "Test", nodes: try makePhotos()) let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, publicAlbumUseCase: publicAlbumUseCase, importPublicAlbumUseCase: MockImportPublicAlbumUseCase( @@ -447,17 +460,14 @@ final class ImportAlbumViewModelTests: XCTestCase { sut.importFolderLocation = NodeEntity(handle: 64, isFolder: true) await sut.importAlbumTask?.value - XCTAssertTrue(sut.showSnackBar) - XCTAssertEqual(sut.snackBarViewModel().snackBar, + XCTAssertEqual(sut.snackBarViewModel?.snackBar, SnackBar(message: Strings.Localizable.AlbumLink.Alert.Message.albumSavedToCloudDrive(newAlbumName))) } func testReservedAlbumNames_onImportAlbumLoad_shouldContainUserAlbumNames() async throws { let userAlbumNames = ["Album 1", "Album 2"] let albumNameUseCase = MockAlbumNameUseCase(userAlbumNames: userAlbumNames) - let album = makeSharedAlbumEntity(set: SetEntity(handle: 24, name: "Test")) - let publicAlbumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(album), - nodes: try makePhotos()) + let publicAlbumUseCase = makePublicAlbumUseCase(handle: 24, name: "Test", nodes: try makePhotos()) let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, publicAlbumUseCase: publicAlbumUseCase, albumNameUseCase: albumNameUseCase) @@ -470,9 +480,7 @@ final class ImportAlbumViewModelTests: XCTestCase { func testRenameAlbumAlertViewModel_albumLoadeded_isConfiguredCorrectly() throws { let albumName = "Test" - let album = makeSharedAlbumEntity(set: SetEntity(handle: 24, name: albumName)) - let publicAlbumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(album), - nodes: try makePhotos()) + let publicAlbumUseCase = makePublicAlbumUseCase(name: albumName, nodes: try makePhotos()) let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, publicAlbumUseCase: publicAlbumUseCase) let expectedAlertViewModel = TextFieldAlertViewModel(title: Strings.Localizable.AlbumLink.Alert.RenameAlbum.title, @@ -485,10 +493,19 @@ final class ImportAlbumViewModelTests: XCTestCase { XCTAssertEqual(alertViewModel, expectedAlertViewModel) } + func testRenameAlbum_noInternetConnection_shouldToggleNoInternetConnection() throws { + let monitorUseCase = MockNetworkMonitorUseCase(connected: false) + let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, + monitorUseCase: monitorUseCase) + XCTAssertFalse(sut.showNoInternetConnection) + + sut.renameAlbum(newName: "Album name") + + XCTAssertTrue(sut.showNoInternetConnection) + } + func testsShouldShowEmptyAlbumView_noPhotos_shouldReturnTrue() async throws { - let sharedAlbum = makeSharedAlbumEntity(set: SetEntity(handle: 1)) - let publicAlbumUseCase = MockPublicAlbumUseCase( - publicAlbumResult: .success(sharedAlbum)) + let publicAlbumUseCase = makePublicAlbumUseCase(handle: 1) let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, publicAlbumUseCase: publicAlbumUseCase) XCTAssertFalse(sut.shouldShowEmptyAlbumView) @@ -500,9 +517,7 @@ final class ImportAlbumViewModelTests: XCTestCase { } func testsShouldShowEmptyAlbumView_photosLoaded_shouldReturnFalse() async throws { - let sharedAlbum = makeSharedAlbumEntity(set: SetEntity(handle: 1)) - let publicAlbumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(sharedAlbum), - nodes: try makePhotos()) + let publicAlbumUseCase = makePublicAlbumUseCase(nodes: try makePhotos()) let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, publicAlbumUseCase: publicAlbumUseCase) @@ -513,8 +528,7 @@ final class ImportAlbumViewModelTests: XCTestCase { } func testIsShareLinkButtonDisabled_onAlbumLoaded_shouldEnableShareButtonEvenIfNoPhotosLoaded() async throws { - let sharedAlbum = makeSharedAlbumEntity(set: SetEntity(handle: 1)) - let publicAlbumUseCase = MockPublicAlbumUseCase(publicAlbumResult: .success(sharedAlbum)) + let publicAlbumUseCase = makePublicAlbumUseCase() let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, publicAlbumUseCase: publicAlbumUseCase) XCTAssertTrue(sut.isShareLinkButtonDisabled) @@ -524,6 +538,113 @@ final class ImportAlbumViewModelTests: XCTestCase { XCTAssertFalse(sut.isShareLinkButtonDisabled) } + func testSaveToPhotos_whenSelectionModeIsActive_shouldSaveSelectedItems() async throws { + // Arrange + let transferWidgetResponder = MockTransferWidgetResponder() + let permissionHandler = MockDevicePermissionHandler( + photoAuthorization: .authorized, + audioAuthorized: false, + videoAuthorized: false, + requestPhotoLibraryAccessPermissionsGranted: true) + let publicAlbumUseCase = makePublicAlbumUseCase() + let saveToPhotosUseCase = MockSaveMediaToPhotosUseCase(saveToPhotosResult: .success(())) + let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, + publicAlbumUseCase: publicAlbumUseCase, + saveMediaUseCase: saveToPhotosUseCase, + transferWidgetResponder: transferWidgetResponder, + permissionHandler: permissionHandler) + await sut.loadPublicAlbum() + + let multiplePhotos = try makePhotos() + sut.enablePhotoLibraryEditMode(true) + sut.photoLibraryContentViewModel.selection.setSelectedPhotos(multiplePhotos) + + // Act + await sut.saveToPhotos() + + // Assert + XCTAssertEqual(transferWidgetResponder.setProgressViewInKeyWindowCalled, 1) + XCTAssertEqual(transferWidgetResponder.bringProgressToFrontKeyWindowIfNeededCalled, 1) + XCTAssertEqual(transferWidgetResponder.updateProgressViewCalled, 1) + XCTAssertEqual(transferWidgetResponder.showWidgetIfNeededCalled, 1) + + XCTAssertEqual(sut.snackBarViewModel?.snackBar, + SnackBar(message: Strings.Localizable.General.SaveToPhotos.started(multiplePhotos.count))) + } + + func testSaveToPhotos_whenSelectionModeNotActive_shouldSaveAllItemsInAlbum() async throws { + // Arrange + let transferWidgetResponder = MockTransferWidgetResponder() + let permissionHandler = MockDevicePermissionHandler( + photoAuthorization: .authorized, + audioAuthorized: false, + videoAuthorized: false, + requestPhotoLibraryAccessPermissionsGranted: true) + let multiplePhotos = try makePhotos() + let publicAlbumUseCase = makePublicAlbumUseCase(nodes: multiplePhotos) + let saveToPhotosUseCase = MockSaveMediaToPhotosUseCase(saveToPhotosResult: .success(())) + let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, + publicAlbumUseCase: publicAlbumUseCase, + saveMediaUseCase: saveToPhotosUseCase, + transferWidgetResponder: transferWidgetResponder, + permissionHandler: permissionHandler) + + await sut.loadPublicAlbum() + + // Act + await sut.saveToPhotos() + + // Assert + XCTAssertEqual(transferWidgetResponder.setProgressViewInKeyWindowCalled, 1) + XCTAssertEqual(transferWidgetResponder.bringProgressToFrontKeyWindowIfNeededCalled, 1) + XCTAssertEqual(transferWidgetResponder.updateProgressViewCalled, 1) + XCTAssertEqual(transferWidgetResponder.showWidgetIfNeededCalled, 1) + XCTAssertEqual(sut.snackBarViewModel?.snackBar, + SnackBar(message: Strings.Localizable.General.SaveToPhotos.started(multiplePhotos.count))) + } + + func testSaveToPhotos_whenPhotosLibraryPermissionRequired_shouldShowAlert() async throws { + // Arrange + let transferWidgetResponder = MockTransferWidgetResponder() + let permissionHandler = MockDevicePermissionHandler( + photoAuthorization: .denied, + audioAuthorized: false, + videoAuthorized: false, + requestPhotoLibraryAccessPermissionsGranted: false) + let publicAlbumUseCase = makePublicAlbumUseCase() + let saveToPhotosUseCase = MockSaveMediaToPhotosUseCase(saveToPhotosResult: .success(())) + let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, + publicAlbumUseCase: publicAlbumUseCase, + saveMediaUseCase: saveToPhotosUseCase, + transferWidgetResponder: transferWidgetResponder, + permissionHandler: permissionHandler) + await sut.loadPublicAlbum() + + let multiplePhotos = try makePhotos() + sut.enablePhotoLibraryEditMode(true) + sut.photoLibraryContentViewModel.selection.setSelectedPhotos(multiplePhotos) + + // Act + await sut.saveToPhotos() + + // Assert + XCTAssertEqual(transferWidgetResponder.setProgressViewInKeyWindowCalled, 0) + XCTAssertEqual(transferWidgetResponder.bringProgressToFrontKeyWindowIfNeededCalled, 0) + XCTAssertNil(sut.snackBarViewModel?.snackBar) + XCTAssertTrue(sut.showPhotoPermissionAlert) + } + + func testSaveToPhotos_noInterNetConnection_shouldToggleNoInternetConnection() async throws { + let monitorUseCase = MockNetworkMonitorUseCase(connected: false) + let sut = makeImportAlbumViewModel(publicLink: try validFullAlbumLink, + monitorUseCase: monitorUseCase) + XCTAssertFalse(sut.showNoInternetConnection) + + await sut.saveToPhotos() + + XCTAssertTrue(sut.showNoInternetConnection) + } + // MARK: - Private private func makeImportAlbumViewModel(publicLink: URL, @@ -532,7 +653,11 @@ final class ImportAlbumViewModelTests: XCTestCase { accountStorageUseCase: some AccountStorageUseCaseProtocol = MockAccountStorageUseCase(), importPublicAlbumUseCase: some ImportPublicAlbumUseCaseProtocol = MockImportPublicAlbumUseCase(), accountUseCase: some AccountUseCaseProtocol = MockAccountUseCase(), - tracker: some AnalyticsTracking = MockTracker() + saveMediaUseCase: some SaveMediaToPhotosUseCaseProtocol = MockSaveMediaToPhotosUseCase(), + transferWidgetResponder: some TransferWidgetResponderProtocol = MockTransferWidgetResponder(), + permissionHandler: some DevicePermissionsHandling = MockDevicePermissionHandler(), + tracker: some AnalyticsTracking = MockTracker(), + monitorUseCase: some NetworkMonitorUseCaseProtocol = MockNetworkMonitorUseCase() ) -> ImportAlbumViewModel { let sut = ImportAlbumViewModel( publicLink: publicLink, @@ -541,7 +666,11 @@ final class ImportAlbumViewModelTests: XCTestCase { accountStorageUseCase: accountStorageUseCase, importPublicAlbumUseCase: importPublicAlbumUseCase, accountUseCase: accountUseCase, - tracker: tracker) + saveMediaUseCase: saveMediaUseCase, + transferWidgetResponder: transferWidgetResponder, + permissionHandler: permissionHandler, + tracker: tracker, + monitorUseCase: monitorUseCase) trackForMemoryLeaks(on: sut) return sut } @@ -551,6 +680,14 @@ final class ImportAlbumViewModelTests: XCTestCase { SharedAlbumEntity(set: set, setElements: setElements) } + private func makeSetElements() -> [SetElementEntity] { + [ + SetElementEntity(handle: 1), + SetElementEntity(handle: 4), + SetElementEntity(handle: 7) + ] + } + private func makePhotos() throws -> [NodeEntity] { [NodeEntity(name: "test_image_1.png", handle: 1, hasThumbnail: true, modificationTime: try "2023-01-01T22:05:04Z".date, mediaType: .image), @@ -560,4 +697,9 @@ final class ImportAlbumViewModelTests: XCTestCase { modificationTime: try "2023-01-01T22:05:04Z".date, mediaType: .image) ] } + + private func makePublicAlbumUseCase(handle: HandleEntity = 1, name: String = "valid album name", nodes: [NodeEntity] = []) -> some PublicAlbumUseCaseProtocol { + let sharedAlbumEntity = makeSharedAlbumEntity(set: SetEntity(handle: 1, name: name)) + return MockPublicAlbumUseCase(publicAlbumResult: .success(sharedAlbumEntity), nodes: nodes) + } } diff --git a/MEGAUnitTests/Analytics/TestHelpers/MockAnalyticsTracker.swift b/MEGAUnitTests/Analytics/TestHelpers/MockAnalyticsTracker.swift index 74d114de2c..ff6ea3cf9a 100644 --- a/MEGAUnitTests/Analytics/TestHelpers/MockAnalyticsTracker.swift +++ b/MEGAUnitTests/Analytics/TestHelpers/MockAnalyticsTracker.swift @@ -4,13 +4,13 @@ import XCTest final class MockTracker: AnalyticsTracking { private(set) var trackedEventIdentifiers: [EventIdentifier] = [] - + init() {} - + func trackAnalyticsEvent(with eventIdentifier: EventIdentifier) { trackedEventIdentifiers.append(eventIdentifier) } - + func assertTrackAnalyticsEventCalled( with expectedEventIdentifiers: [EventIdentifier], file: StaticString = #file, line: UInt = #line @@ -20,7 +20,7 @@ final class MockTracker: AnalyticsTracking { expectedEventIdentifiers.count, file: file, line: line ) - + for (tracked, expected) in zip(expectedEventIdentifiers, trackedEventIdentifiers) { XCTAssertEqual( tracked.stringValue, diff --git a/MEGAUnitTests/AudioPlayer/Repository/Mocks/MockAudioPlayerHandler.swift b/MEGAUnitTests/AudioPlayer/Repository/Mocks/MockAudioPlayerHandler.swift index 8e6ba4e85f..a8f406ed22 100644 --- a/MEGAUnitTests/AudioPlayer/Repository/Mocks/MockAudioPlayerHandler.swift +++ b/MEGAUnitTests/AudioPlayer/Repository/Mocks/MockAudioPlayerHandler.swift @@ -25,6 +25,7 @@ final class MockAudioPlayerHandler: AudioPlayerHandlerProtocol { var playItem_calledTimes = 0 var changePlayerRate_calledTimes = 0 var setCurrent_callTimes = 0 + var initMiniPlayerCallCount = 0 private var _isPlayerDefined = false @@ -139,7 +140,9 @@ final class MockAudioPlayerHandler: AudioPlayerHandlerProtocol { func playerCurrentItemTime() -> TimeInterval { 0.0 } func playerQueueItems() -> [AudioPlayerItem]? { nil } func playerPlaylistItems() -> [AudioPlayerItem]? { nil} - func initMiniPlayer(node: MEGANode?, fileLink: String?, filePaths: [String]?, isFolderLink: Bool, presenter: UIViewController, shouldReloadPlayerInfo: Bool, shouldResetPlayer: Bool) {} + func initMiniPlayer(node: MEGANode?, fileLink: String?, filePaths: [String]?, isFolderLink: Bool, presenter: UIViewController, shouldReloadPlayerInfo: Bool, shouldResetPlayer: Bool) { + initMiniPlayerCallCount += 1 + } func initFullScreenPlayer(node: MEGANode?, fileLink: String?, filePaths: [String]?, isFolderLink: Bool, presenter: UIViewController, messageId: HandleEntity, chatId: HandleEntity, allNodes: [MEGANode]?) {} func playerHidden(_ hidden: Bool, presenter: UIViewController) {} func closePlayer() {} diff --git a/MEGAUnitTests/AudioPlayer/Routers/MockAudioPlaylistViewRouter.swift b/MEGAUnitTests/AudioPlayer/Routers/MockAudioPlaylistViewRouter.swift index 946993db33..bdef58181c 100644 --- a/MEGAUnitTests/AudioPlayer/Routers/MockAudioPlaylistViewRouter.swift +++ b/MEGAUnitTests/AudioPlayer/Routers/MockAudioPlaylistViewRouter.swift @@ -3,6 +3,7 @@ final class MockAudioPlaylistViewRouter: AudioPlaylistViewRouting { var dismiss_calledTimes = 0 var navigateToDetail_calledTimes = 0 + var start_calledTimes = 0 func dismiss() { dismiss_calledTimes += 1 @@ -11,4 +12,8 @@ final class MockAudioPlaylistViewRouter: AudioPlaylistViewRouting { func navigateToDetail(node: MEGANode) { navigateToDetail_calledTimes += 1 } + + func start() { + start_calledTimes += 1 + } } diff --git a/MEGAUnitTests/AudioPlayer/Scenes/AudioPlayerScenes/AudioPlayerViewRouterNodeActionAdapterTests.swift b/MEGAUnitTests/AudioPlayer/Scenes/AudioPlayerScenes/AudioPlayerViewRouterNodeActionAdapterTests.swift new file mode 100644 index 0000000000..e6a6d1ec35 --- /dev/null +++ b/MEGAUnitTests/AudioPlayer/Scenes/AudioPlayerScenes/AudioPlayerViewRouterNodeActionAdapterTests.swift @@ -0,0 +1,95 @@ +@testable import MEGA +import MEGADomain +import MEGASDKRepoMock +import MEGATest +import XCTest + +final class AudioPlayerViewRouterNodeActionAdapterTests: XCTestCase { + + func testNodeAction_fromFileLinkNode_fordwardsCorrectDelegate() { + let link = "any-file-link" + let sut = makeSUT(configEntity: audioPlayerConfigEntity(from: .fileLink, fileLink: link, relatedFiles: nil)) + + sut.nodeAction(mockNodeActionViewController(), didSelect: .rename, for: MockNode(handle: 1), from: "any-sender") + + guard let mockNodeActionDelegate = sut.nodeActionViewControllerDelegate as? MockNodeActionViewControllerGenericDelegate else { + XCTFail("Expect to have mock type of : \(type(of: MockNodeActionViewControllerGenericDelegate.self))") + return + } + XCTAssertEqual(mockNodeActionDelegate.didSelectNodeActionCallCount, 0) + } + + func testNodeAction_fromNonFileLinkNode_fordwardsCorrectDelegate() { + AudioPlayerConfigEntity.NodeOriginType.allCases + .filter { $0 == .fileLink } + .map { audioPlayerConfigEntity(from: $0) } + .enumerated() + .forEach { (index, configEntity) in + + let sut = makeSUT(configEntity: configEntity) + + sut.nodeAction(mockNodeActionViewController(), didSelect: .rename, for: MockNode(handle: 1), from: "any-sender") + + guard let mockNodeActionDelegate = sut.nodeActionViewControllerDelegate as? MockNodeActionViewControllerGenericDelegate else { + XCTFail("Expect to have mock type of : \(type(of: MockNodeActionViewControllerGenericDelegate.self))") + return + } + XCTAssertEqual(mockNodeActionDelegate.didSelectNodeActionCallCount, 1, "Fail at index: \(index)") + } + } + + // MARK: - Helpers + + private func makeSUT(configEntity: AudioPlayerConfigEntity, file: StaticString = #filePath, line: UInt = #line) -> AudioPlayerViewRouterNodeActionAdapter { + let sut = AudioPlayerViewRouterNodeActionAdapter( + configEntity: configEntity, + nodeActionViewControllerDelegate: MockNodeActionViewControllerGenericDelegate(viewController: UIViewController()), + fileLinkActionViewControllerDelegate: FileLinkActionViewControllerDelegate(link: "any-link", viewController: UIViewController()) + ) + trackForMemoryLeaks(on: sut, file: file, line: line) + return sut + } + + private func mockNodeActionViewController() -> NodeActionViewController { + NodeActionViewController( + node: MockNode(handle: 1), + delegate: MockNodeActionViewControllerGenericDelegate(viewController: UIViewController()), + displayMode: .albumLink, + isInVersionsView: false, + isBackupNode: false, + sender: "any-sender" + ) + } + + private func audioPlayerConfigEntity( + from originType: AudioPlayerConfigEntity.NodeOriginType, + fileLink: String? = nil, + messageId: HandleEntity? = nil, + chatId: HandleEntity? = nil, + relatedFiles: [String]? = nil, + playerHandler: MockAudioPlayerHandler = MockAudioPlayerHandler() + ) -> AudioPlayerConfigEntity { + let node = MockNode(handle: .max) + + switch originType { + case .folderLink: + return AudioPlayerConfigEntity(node: node, isFolderLink: true, fileLink: nil, messageId: .invalid, chatId: .invalid, relatedFiles: relatedFiles, playerHandler: playerHandler) + case .fileLink: + return AudioPlayerConfigEntity(node: node, isFolderLink: false, fileLink: fileLink, messageId: .invalid, chatId: .invalid, relatedFiles: relatedFiles, playerHandler: playerHandler) + case .chat: + return AudioPlayerConfigEntity(node: node, isFolderLink: false, fileLink: nil, messageId: messageId, chatId: chatId, relatedFiles: relatedFiles, playerHandler: playerHandler) + case .unknown: + return AudioPlayerConfigEntity(node: node, isFolderLink: false, fileLink: nil, messageId: .invalid, chatId: .invalid, relatedFiles: relatedFiles, playerHandler: playerHandler) + } + } + +} + +private final class MockNodeActionViewControllerGenericDelegate: NodeActionViewControllerGenericDelegate { + + private(set) var didSelectNodeActionCallCount = 0 + + override func nodeAction(_ nodeAction: NodeActionViewController, didSelect action: MegaNodeActionType, for node: MEGANode, from sender: Any) { + didSelectNodeActionCallCount += 1 + } +} diff --git a/MEGAUnitTests/AudioPlayer/Scenes/AudioPlayerScenes/AudioPlayerViewRouterTests.swift b/MEGAUnitTests/AudioPlayer/Scenes/AudioPlayerScenes/AudioPlayerViewRouterTests.swift index 61dad2472c..7f258c31a0 100644 --- a/MEGAUnitTests/AudioPlayer/Scenes/AudioPlayerScenes/AudioPlayerViewRouterTests.swift +++ b/MEGAUnitTests/AudioPlayer/Scenes/AudioPlayerScenes/AudioPlayerViewRouterTests.swift @@ -7,7 +7,7 @@ import XCTest final class AudioPlayerViewRouterTests: XCTestCase { func testBuild_whenNodeIsFolderLink_configCorrectDelegate() { - let sut = makeSUT(nodeOriginType: .folderLink) + let (sut, _, _, _, _, _) = makeSUT(nodeOriginType: .folderLink) _ = sut.build() @@ -15,7 +15,7 @@ final class AudioPlayerViewRouterTests: XCTestCase { } func testBuild_whenNodeIsFileLink_configCorrectDelegate() { - let sut = makeSUT(nodeOriginType: .fileLink, fileLink: anyFileLink()) + let (sut, _, _, _, _, _) = makeSUT(nodeOriginType: .fileLink, fileLink: anyFileLink()) _ = sut.build() @@ -25,35 +25,101 @@ final class AudioPlayerViewRouterTests: XCTestCase { func testBuild_whenNodeIsFromChat_configCorrectDelegate() { let expectedChatId = anyHandleEntity() let expectedMessageId = anyHandleEntity() - let sut = makeSUT(nodeOriginType: .chat, messageId: expectedMessageId, chatId: expectedChatId) + let (sut, _, _, _, _, _) = makeSUT(nodeOriginType: .chat, messageId: expectedMessageId, chatId: expectedChatId) _ = sut.build() assertThatCorrectDelegateConfiguredWhenNodeIsFromChat(on: sut, expectedChatId, expectedMessageId) } + func testStart_presentBuildedView() { + let (sut, presenter, _, _, _, _) = makeSUT() + + sut.start() + + XCTAssertEqual(presenter.presentCallCount, 1) + } + + func testShowMiniPlayer_withNode_showsMiniPlayer() { + let (sut, _, _, mockPlayerHandler, _, _) = makeSUT() + sut.start() + + sut.showMiniPlayer(node: MockNode(handle: 1), shouldReload: false) + + XCTAssertEqual(mockPlayerHandler.initMiniPlayerCallCount, 1) + } + + func testShowMiniPlayer_withouthNode_showsMiniPlayer() { + let (sut, _, _, mockPlayerHandler, _, _) = makeSUT() + sut.start() + + sut.showMiniPlayer(node: nil, shouldReload: false) + + XCTAssertEqual(mockPlayerHandler.initMiniPlayerCallCount, 1) + } + + func testShowMiniPlayer_withFile_showsMiniPlayer() { + let (sut, _, _, mockPlayerHandler, _, _) = makeSUT() + sut.start() + + sut.showMiniPlayer(file: "any-file", shouldReload: false) + + XCTAssertEqual(mockPlayerHandler.initMiniPlayerCallCount, 1) + } + + func testGoToPlaylist_whenCalled_startsPlaylistRouter() { + let (sut, _, _, _, audioPlaylistViewRouter, _) = makeSUT() + + sut.goToPlaylist() + + XCTAssertEqual(audioPlaylistViewRouter.start_calledTimes, 1) + } + + func testDismiss_whenCalled_dismissView() { + let (sut, _, _, _, _, audioPlayerViewController) = makeSUT(audioPlayerViewController: MockViewController()) + + sut.dismiss() + + XCTAssertEqual(audioPlayerViewController.dismissCallCount, 1) + } + + func testShowAction_whenCalled_presentNodeActionView() { + let (sut, _, _, _, _, audioPlayerViewController) = makeSUT(audioPlayerViewController: MockViewController()) + + sut.showAction(for: MockNode(handle: 1), sender: "any-sender") + + XCTAssertEqual(audioPlayerViewController.presentCallCount, 1) + } + // MARK: - Helpers private func makeSUT( - nodeOriginType originType: AudioPlayerConfigEntity.NodeOriginType, + nodeOriginType originType: AudioPlayerConfigEntity.NodeOriginType = .folderLink, fileLink: String? = nil, messageId: HandleEntity? = nil, chatId: HandleEntity? = nil, + relatedFiles: [String]? = nil, + audioPlayerViewController: MockViewController = MockViewController(), file: StaticString = #filePath, line: UInt = #line - ) -> AudioPlayerViewRouter { - let currentContextViewController = UIViewController() + ) -> (sut: AudioPlayerViewRouter, presenter: MockViewController, configEntity: AudioPlayerConfigEntity, mockPlayerHandler: MockAudioPlayerHandler, audioPlaylistViewRouter: MockAudioPlaylistViewRouter, baseViewController: MockViewController) { + let currentContextViewController = MockViewController() + let audioPlaylistViewRouter = MockAudioPlaylistViewRouter() + let (configEntity, mockPlayerHandler) = audioPlayerConfigEntity( + from: originType, + fileLink: fileLink, + messageId: messageId, + chatId: chatId, + relatedFiles: relatedFiles + ) let sut = AudioPlayerViewRouter( - configEntity: audioPlayerConfigEntity( - from: originType, - fileLink: fileLink, - messageId: messageId, - chatId: chatId - ), - presenter: currentContextViewController + configEntity: configEntity, + presenter: currentContextViewController, + audioPlaylistViewRouter: audioPlaylistViewRouter ) + sut.baseViewController = audioPlayerViewController trackForMemoryLeaks(on: sut, file: file, line: line) - return sut + return (sut, currentContextViewController, configEntity, mockPlayerHandler, audioPlaylistViewRouter, audioPlayerViewController) } private func assertThatCorrectDelegateConfiguredWhenNodeIsFromFolderLink(on sut: AudioPlayerViewRouter, file: StaticString = #filePath, line: UInt = #line) { @@ -83,20 +149,21 @@ final class AudioPlayerViewRouterTests: XCTestCase { from originType: AudioPlayerConfigEntity.NodeOriginType, fileLink: String? = nil, messageId: HandleEntity? = nil, - chatId: HandleEntity? = nil - ) -> AudioPlayerConfigEntity { + chatId: HandleEntity? = nil, + relatedFiles: [String]? = nil, + playerHandler: MockAudioPlayerHandler = MockAudioPlayerHandler() + ) -> (configEntity: AudioPlayerConfigEntity, playerHandler: MockAudioPlayerHandler) { let node = MockNode(handle: .max) - let playerHandler = MockAudioPlayerHandler() switch originType { case .folderLink: - return AudioPlayerConfigEntity(node: node, isFolderLink: true, fileLink: nil, messageId: .invalid, chatId: .invalid, relatedFiles: nil, playerHandler: playerHandler) + return (AudioPlayerConfigEntity(node: node, isFolderLink: true, fileLink: nil, messageId: .invalid, chatId: .invalid, relatedFiles: relatedFiles, playerHandler: playerHandler), playerHandler) case .fileLink: - return AudioPlayerConfigEntity(node: node, isFolderLink: false, fileLink: fileLink, messageId: .invalid, chatId: .invalid, relatedFiles: nil, playerHandler: playerHandler) + return (AudioPlayerConfigEntity(node: node, isFolderLink: false, fileLink: fileLink, messageId: .invalid, chatId: .invalid, relatedFiles: relatedFiles, playerHandler: playerHandler), playerHandler) case .chat: - return AudioPlayerConfigEntity(node: node, isFolderLink: false, fileLink: nil, messageId: messageId, chatId: chatId, relatedFiles: nil, playerHandler: playerHandler) + return (AudioPlayerConfigEntity(node: node, isFolderLink: false, fileLink: nil, messageId: messageId, chatId: chatId, relatedFiles: relatedFiles, playerHandler: playerHandler), playerHandler) case .unknown: - return AudioPlayerConfigEntity(node: node, isFolderLink: false, fileLink: nil, messageId: .invalid, chatId: .invalid, relatedFiles: nil, playerHandler: playerHandler) + return (AudioPlayerConfigEntity(node: node, isFolderLink: false, fileLink: nil, messageId: .invalid, chatId: .invalid, relatedFiles: relatedFiles, playerHandler: playerHandler), playerHandler) } } @@ -113,3 +180,16 @@ final class AudioPlayerViewRouterTests: XCTestCase { } } + +private class MockViewController: UIViewController { + private(set) var presentCallCount = 0 + private(set) var dismissCallCount = 0 + + override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + presentCallCount += 1 + } + + override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + dismissCallCount += 1 + } +} diff --git a/MEGAUnitTests/AudioPlayer/ViewModel/AudioPlayerViewModelTests.swift b/MEGAUnitTests/AudioPlayer/ViewModel/AudioPlayerViewModelTests.swift index dff374bbef..be9a78b58f 100644 --- a/MEGAUnitTests/AudioPlayer/ViewModel/AudioPlayerViewModelTests.swift +++ b/MEGAUnitTests/AudioPlayer/ViewModel/AudioPlayerViewModelTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import MEGASDKRepoMock import XCTest diff --git a/MEGAUnitTests/CameraUpload/CameraUploadConcurrentCountCalculatorTests.m b/MEGAUnitTests/CameraUpload/CameraUploadConcurrentCountCalculatorTests.m index 456717336c..437fc5afc9 100644 --- a/MEGAUnitTests/CameraUpload/CameraUploadConcurrentCountCalculatorTests.m +++ b/MEGAUnitTests/CameraUpload/CameraUploadConcurrentCountCalculatorTests.m @@ -1,4 +1,3 @@ - #import #import "CameraUploadConcurrentCountCalculator.h" diff --git a/MEGAUnitTests/CameraUpload/PhotoAlbumContainerViewModelTests.swift b/MEGAUnitTests/CameraUpload/PhotoAlbumContainerViewModelTests.swift index ae5113674a..1e4e55b1b9 100644 --- a/MEGAUnitTests/CameraUpload/PhotoAlbumContainerViewModelTests.swift +++ b/MEGAUnitTests/CameraUpload/PhotoAlbumContainerViewModelTests.swift @@ -13,4 +13,13 @@ final class PhotoAlbumContainerViewModelTests: XCTestCase { mockTracker.assertTrackAnalyticsEventCalled(with: [PhotoScreenEvent()]) } + func testShareLinksTapped_shouldSetShowShareAlbumLinksToTrueAndTrackEvent() { + let mockTracker = MockTracker() + let sut = PhotoAlbumContainerViewModel(tracker: mockTracker) + + sut.shareLinksTapped() + + XCTAssertTrue(sut.showShareAlbumLinks) + mockTracker.assertTrackAnalyticsEventCalled(with: [AlbumListShareLinkMenuItemEvent()]) + } } diff --git a/MEGAUnitTests/Chat/ActiveCallViewModelTests.swift b/MEGAUnitTests/Chat/ActiveCallViewModelTests.swift index 14c16f9e7a..2b920ba065 100644 --- a/MEGAUnitTests/Chat/ActiveCallViewModelTests.swift +++ b/MEGAUnitTests/Chat/ActiveCallViewModelTests.swift @@ -2,6 +2,7 @@ import Combine @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class ActiveCallViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/Chat/ChatContentViewModelTests.swift b/MEGAUnitTests/Chat/ChatContentViewModelTests.swift index abf506c493..e12d1b7fb7 100644 --- a/MEGAUnitTests/Chat/ChatContentViewModelTests.swift +++ b/MEGAUnitTests/Chat/ChatContentViewModelTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class ChatContentViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/Chat/ChatRoomsListViewModelTests.swift b/MEGAUnitTests/Chat/ChatRoomsListViewModelTests.swift index c2afb5dfa3..80cedc5708 100644 --- a/MEGAUnitTests/Chat/ChatRoomsListViewModelTests.swift +++ b/MEGAUnitTests/Chat/ChatRoomsListViewModelTests.swift @@ -2,6 +2,7 @@ import Combine @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import MEGAPermissions import MEGAPermissionsMock import XCTest @@ -367,13 +368,13 @@ final class ChatRoomsListViewModelTests: XCTestCase { } final class MockChatRoomsListRouter: ChatRoomsListRouting { - var openCallView_calledTimes = 0 var presentStartConversation_calledTimes = 0 - var presentMeetingAlreayExists_calledTimes = 0 + var presentMeetingAlreadyExists_calledTimes = 0 var presentCreateMeeting_calledTimes = 0 var presentEnterMeeting_calledTimes = 0 var presentScheduleMeeting_calledTimes = 0 + var presentWaitingRoom_calledTimes = 0 var showInviteContactScreen_calledTimes = 0 var showContactsOnMegaScreen_calledTimes = 0 var showDetails_calledTimes = 0 @@ -395,8 +396,8 @@ final class MockChatRoomsListRouter: ChatRoomsListRouting { presentStartConversation_calledTimes += 1 } - func presentMeetingAlreayExists() { - presentMeetingAlreayExists_calledTimes += 1 + func presentMeetingAlreadyExists() { + presentMeetingAlreadyExists_calledTimes += 1 } func presentCreateMeeting() { @@ -411,6 +412,10 @@ final class MockChatRoomsListRouter: ChatRoomsListRouting { presentScheduleMeeting_calledTimes += 1 } + func presentWaitingRoom(for scheduledMeeting: ScheduledMeetingEntity) { + presentWaitingRoom_calledTimes += 1 + } + func showInviteContactScreen() { showInviteContactScreen_calledTimes += 1 } diff --git a/MEGAUnitTests/CloudDrive/CopyrightWarning/EnforceCopyrightWarningViewModelTests.swift b/MEGAUnitTests/CloudDrive/CopyrightWarning/EnforceCopyrightWarningViewModelTests.swift index c127428d27..58212ca4df 100644 --- a/MEGAUnitTests/CloudDrive/CopyrightWarning/EnforceCopyrightWarningViewModelTests.swift +++ b/MEGAUnitTests/CloudDrive/CopyrightWarning/EnforceCopyrightWarningViewModelTests.swift @@ -2,6 +2,7 @@ import Combine @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class EnforceCopyrightWarningViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/CloudDrive/GetLink/Cells/GetLinkAlbumInfoCellViewModelTests.swift b/MEGAUnitTests/CloudDrive/GetLink/Cells/GetLinkAlbumInfoCellViewModelTests.swift index a5a7e5af5b..fddb2e862a 100644 --- a/MEGAUnitTests/CloudDrive/GetLink/Cells/GetLinkAlbumInfoCellViewModelTests.swift +++ b/MEGAUnitTests/CloudDrive/GetLink/Cells/GetLinkAlbumInfoCellViewModelTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class GetLinkAlbumInfoCellViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/CloudDrive/GetLink/GetAlbumLinkViewModelTests.swift b/MEGAUnitTests/CloudDrive/GetLink/GetAlbumLinkViewModelTests.swift index 8dec746598..4a86469e55 100644 --- a/MEGAUnitTests/CloudDrive/GetLink/GetAlbumLinkViewModelTests.swift +++ b/MEGAUnitTests/CloudDrive/GetLink/GetAlbumLinkViewModelTests.swift @@ -1,6 +1,9 @@ @testable import MEGA +import MEGAAnalyticsiOS import MEGADomain import MEGADomainMock +import MEGAL10n +import MEGAPresentation import XCTest final class GetAlbumLinkViewModelTests: XCTestCase { @@ -11,7 +14,8 @@ final class GetAlbumLinkViewModelTests: XCTestCase { GetLinkSectionViewModel(sectionType: .info, cellViewModels: [], itemHandle: album.id) ] - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: MockShareAlbumUseCase(), sectionViewModels: sections) + let sut = makeGetAlbumLinkViewModel(album: album, + sectionViewModels: sections) XCTAssertEqual(sut.numberOfSections, sections.count) } @@ -24,7 +28,9 @@ final class GetAlbumLinkViewModelTests: XCTestCase { itemHandle: album.id) ] - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: MockShareAlbumUseCase(), sectionViewModels: sections) + let sut = makeGetAlbumLinkViewModel(album: album, + sectionViewModels: sections) + XCTAssertEqual(sut.numberOfRowsInSection(0), cellViewModels.count) } @@ -37,8 +43,8 @@ final class GetAlbumLinkViewModelTests: XCTestCase { cellViewModels: cellViewModels, itemHandle: album.id) ] - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: MockShareAlbumUseCase(), - sectionViewModels: sections) + let sut = makeGetAlbumLinkViewModel(album: album, + sectionViewModels: sections) let indexPath = IndexPath(row: 0, section: 0) XCTAssertEqual(sut.cellViewModel(indexPath: indexPath)?.type, cellViewModels[indexPath.row].type @@ -50,16 +56,17 @@ final class GetAlbumLinkViewModelTests: XCTestCase { let section = GetLinkSectionViewModel(sectionType: .info, cellViewModels: [], itemHandle: album.id) - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: MockShareAlbumUseCase(), - sectionViewModels: [section]) + let sut = makeGetAlbumLinkViewModel(album: album, + sectionViewModels: [section]) XCTAssertEqual(sut.sectionType(forSection: 0), section.sectionType) } - func testDispatchViewConfiguration_onNoExportedAlbums_shouldSetTitleToShareLink() { + func testDispatchViewConfiguration_onNoExportedAlbums_shouldSetTitleToShareLinkAndTrackScreen() { let album = AlbumEntity(id: 1, type: .user, sharedLinkStatus: .exported(false)) - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: MockShareAlbumUseCase(), - sectionViewModels: []) + let tracker = MockTracker() + let sut = makeGetAlbumLinkViewModel(album: album, + tracker: tracker) let expectedTitle = Strings.Localizable.General.MenuAction.ShareLink.title(1) test(viewModel: sut, action: .onViewReady, expectedCommands: [ @@ -67,12 +74,17 @@ final class GetAlbumLinkViewModelTests: XCTestCase { isMultilink: false, shareButtonTitle: Strings.Localizable.General.MenuAction.ShareLink.title(1)) ]) + + tracker.assertTrackAnalyticsEventCalled( + with: [ + SingleAlbumLinkScreenEvent() + ] + ) } func testDispatchViewConfiguration_onExportedAlbums_shouldSetTitleToManageShareLink() { let album = AlbumEntity(id: 1, type: .user, sharedLinkStatus: .exported(true)) - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: MockShareAlbumUseCase(), - sectionViewModels: []) + let sut = makeGetAlbumLinkViewModel(album: album) let expectedTitle = Strings.Localizable.General.MenuAction.ManageLink.title(1) test(viewModel: sut, action: .onViewReady, expectedCommands: [ @@ -91,8 +103,10 @@ final class GetAlbumLinkViewModelTests: XCTestCase { ] let link = "the shared link" let shareAlbumUseCase = MockShareAlbumUseCase(shareAlbumLinkResult: .success(link)) - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: shareAlbumUseCase, - sectionViewModels: sections) + let sut = makeGetAlbumLinkViewModel(album: album, + shareAlbumUseCase: shareAlbumUseCase, + sectionViewModels: sections) + let updatedIndexPath = IndexPath(row: 0, section: 0) test(viewModel: sut, action: .onViewReady, expectedCommands: [ .configureView(title: Strings.Localizable.General.MenuAction.ShareLink.title(1), @@ -123,8 +137,9 @@ final class GetAlbumLinkViewModelTests: XCTestCase { ] let link = "/collection/link#key" let shareAlbumUseCase = MockShareAlbumUseCase(shareAlbumLinkResult: .success(link)) - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: shareAlbumUseCase, - sectionViewModels: sections) + let sut = makeGetAlbumLinkViewModel(album: album, + shareAlbumUseCase: shareAlbumUseCase, + sectionViewModels: sections) test(viewModel: sut, action: .onViewReady, expectedCommands: [ .configureView(title: Strings.Localizable.General.MenuAction.ShareLink.title(1), isMultilink: false, @@ -164,8 +179,9 @@ final class GetAlbumLinkViewModelTests: XCTestCase { itemHandle: album.id) ] let shareAlbumUseCase = MockShareAlbumUseCase(shareAlbumLinkResult: .success(link)) - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: shareAlbumUseCase, - sectionViewModels: sections) + let sut = makeGetAlbumLinkViewModel(album: album, + shareAlbumUseCase: shareAlbumUseCase, + sectionViewModels: sections) test(viewModel: sut, action: .onViewReady, expectedCommands: [ .configureView(title: Strings.Localizable.General.MenuAction.ShareLink.title(1), @@ -193,8 +209,9 @@ final class GetAlbumLinkViewModelTests: XCTestCase { itemHandle: album.id) ] let shareAlbumUseCase = MockShareAlbumUseCase(shareAlbumLinkResult: .success(link)) - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: shareAlbumUseCase, - sectionViewModels: sections) + let sut = makeGetAlbumLinkViewModel(album: album, + shareAlbumUseCase: shareAlbumUseCase, + sectionViewModels: sections) test(viewModel: sut, action: .onViewReady, expectedCommands: [ .configureView(title: Strings.Localizable.General.MenuAction.ShareLink.title(1), @@ -220,8 +237,9 @@ final class GetAlbumLinkViewModelTests: XCTestCase { itemHandle: album.id) ] let shareAlbumUseCase = MockShareAlbumUseCase(shareAlbumLinkResult: .success(link)) - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: shareAlbumUseCase, - sectionViewModels: sections) + let sut = makeGetAlbumLinkViewModel(album: album, + shareAlbumUseCase: shareAlbumUseCase, + sectionViewModels: sections) test(viewModel: sut, action: .onViewReady, expectedCommands: [ .configureView(title: Strings.Localizable.General.MenuAction.ShareLink.title(1), @@ -252,8 +270,9 @@ final class GetAlbumLinkViewModelTests: XCTestCase { itemHandle: album.id) ] let shareAlbumUseCase = MockShareAlbumUseCase(shareAlbumLinkResult: .success(link)) - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: shareAlbumUseCase, - sectionViewModels: sections) + let sut = makeGetAlbumLinkViewModel(album: album, + shareAlbumUseCase: shareAlbumUseCase, + sectionViewModels: sections) test(viewModel: sut, action: .onViewReady, expectedCommands: [ .configureView(title: Strings.Localizable.General.MenuAction.ShareLink.title(1), @@ -284,8 +303,9 @@ final class GetAlbumLinkViewModelTests: XCTestCase { itemHandle: album.id) ] let shareAlbumUseCase = MockShareAlbumUseCase(shareAlbumLinkResult: .success(link)) - let sut = GetAlbumLinkViewModel(album: album, shareAlbumUseCase: shareAlbumUseCase, - sectionViewModels: sections) + let sut = makeGetAlbumLinkViewModel(album: album, + shareAlbumUseCase: shareAlbumUseCase, + sectionViewModels: sections) test(viewModel: sut, action: .onViewReady, expectedCommands: [ .configureView(title: Strings.Localizable.General.MenuAction.ShareLink.title(1), @@ -303,4 +323,18 @@ final class GetAlbumLinkViewModelTests: XCTestCase { Strings.Localizable.keyCopiedToClipboard)) ]) } + + // MARK: - Helpers + + private func makeGetAlbumLinkViewModel( + album: AlbumEntity, + shareAlbumUseCase: some ShareAlbumUseCaseProtocol = MockShareAlbumUseCase(), + sectionViewModels: [GetLinkSectionViewModel] = [], + tracker: some AnalyticsTracking = MockTracker() + ) -> GetAlbumLinkViewModel { + GetAlbumLinkViewModel(album: album, + shareAlbumUseCase: shareAlbumUseCase, + sectionViewModels: sectionViewModels, + tracker: tracker) + } } diff --git a/MEGAUnitTests/CloudDrive/GetLink/GetAlbumsLinkViewModelTests.swift b/MEGAUnitTests/CloudDrive/GetLink/GetAlbumsLinkViewModelTests.swift index fe2bad6664..ea98aff5bc 100644 --- a/MEGAUnitTests/CloudDrive/GetLink/GetAlbumsLinkViewModelTests.swift +++ b/MEGAUnitTests/CloudDrive/GetLink/GetAlbumsLinkViewModelTests.swift @@ -1,6 +1,9 @@ @testable import MEGA +import MEGAAnalyticsiOS import MEGADomain import MEGADomainMock +import MEGAL10n +import MEGAPresentation import XCTest final class GetAlbumsLinkViewModelTests: XCTestCase { @@ -10,9 +13,9 @@ final class GetAlbumsLinkViewModelTests: XCTestCase { let sections = [ GetLinkSectionViewModel(sectionType: .info, cellViewModels: [], itemHandle: album.id) ] - let sut = GetAlbumsLinkViewModel(albums: [album], - shareAlbumUseCase: MockShareAlbumUseCase(), - sectionViewModels: sections) + let sut = makeGetAlbumsLinkViewModel(albums: [album], + sectionViewModels: sections) + XCTAssertEqual(sut.numberOfSections, sections.count) } @@ -24,9 +27,9 @@ final class GetAlbumsLinkViewModelTests: XCTestCase { cellViewModels: cellViewModels, itemHandle: album.id) ] - let sut = GetAlbumsLinkViewModel(albums: [album], - shareAlbumUseCase: MockShareAlbumUseCase(), - sectionViewModels: sections) + let sut = makeGetAlbumsLinkViewModel(albums: [album], + sectionViewModels: sections) + XCTAssertEqual(sut.numberOfRowsInSection(0), cellViewModels.count) } @@ -39,9 +42,9 @@ final class GetAlbumsLinkViewModelTests: XCTestCase { cellViewModels: cellViewModels, itemHandle: album.id) ] - let sut = GetAlbumsLinkViewModel(albums: [album], - shareAlbumUseCase: MockShareAlbumUseCase(), - sectionViewModels: sections) + let sut = makeGetAlbumsLinkViewModel(albums: [album], + sectionViewModels: sections) + let indexPath = IndexPath(row: 0, section: 0) XCTAssertEqual(sut.cellViewModel(indexPath: indexPath)?.type, cellViewModels[indexPath.row].type @@ -53,16 +56,18 @@ final class GetAlbumsLinkViewModelTests: XCTestCase { let section = GetLinkSectionViewModel(sectionType: .info, cellViewModels: [], itemHandle: album.id) - let sut = GetAlbumsLinkViewModel(albums: [album], - shareAlbumUseCase: MockShareAlbumUseCase(), - sectionViewModels: [section]) + let sut = makeGetAlbumsLinkViewModel(albums: [album], + sectionViewModels: [section]) + XCTAssertEqual(sut.sectionType(forSection: 0), section.sectionType) } - func testDispatch_onViewReady_shouldSetTitleToShareLink() throws { + func testDispatch_onViewReady_shouldSetTitleToShareLinkAndTrackEvent() throws { let albums = [AlbumEntity(id: 1, type: .user), AlbumEntity(id: 2, type: .user)] - let sut = GetAlbumsLinkViewModel(albums: albums, shareAlbumUseCase: MockShareAlbumUseCase(), sectionViewModels: []) + let tracker = MockTracker() + let sut = makeGetAlbumsLinkViewModel(albums: albums, + tracker: tracker) let expectedTitle = Strings.Localizable.General.MenuAction.ShareLink.title(albums.count) test(viewModel: sut, action: .onViewReady, expectedCommands: [ @@ -73,6 +78,12 @@ final class GetAlbumsLinkViewModelTests: XCTestCase { .showHud(.status(Strings.Localizable.generatingLinks)), .dismissHud ]) + + tracker.assertTrackAnalyticsEventCalled( + with: [ + MultipleAlbumLinksScreenEvent() + ] + ) } func testDispatch_onViewReadyLinksLoaded_shouldUpdateLinkCells() throws { @@ -88,9 +99,10 @@ final class GetAlbumsLinkViewModelTests: XCTestCase { IndexPath(row: 0, section: $0) } let links = [firstAlbum.id: "link1", secondAlbum.id: "link2"] - let sut = GetAlbumsLinkViewModel(albums: albums, - shareAlbumUseCase: MockShareAlbumUseCase(shareAlbumsLinks: links), - sectionViewModels: sections) + let sut = makeGetAlbumsLinkViewModel(albums: albums, + shareAlbumUseCase: MockShareAlbumUseCase(shareAlbumsLinks: links), + sectionViewModels: sections) + expectSuccessfulOnViewReady(sut: sut, albums: albums, expectedRowReload: expectedRowReloads) try expectedRowReloads.forEach { index in @@ -108,13 +120,15 @@ final class GetAlbumsLinkViewModelTests: XCTestCase { ], itemHandle: $0.id) } let links = Dictionary(uniqueKeysWithValues: albums.map { ($0.id, "link-\($0.id)") }) - let sut = GetAlbumsLinkViewModel(albums: albums, + let sut = makeGetAlbumsLinkViewModel(albums: albums, shareAlbumUseCase: MockShareAlbumUseCase(shareAlbumsLinks: links), sectionViewModels: sections) let expectedRowReloads = sections.indices.map { IndexPath(row: 0, section: $0) } - expectSuccessfulOnViewReady(sut: sut, albums: albums, expectedRowReload: expectedRowReloads) + expectSuccessfulOnViewReady(sut: sut, albums: albums, + expectedRowReload: expectedRowReloads) + let expectedLink = links.values.joined(separator: "\n") let barButton = UIBarButtonItem() test(viewModel: sut, action: .shareLink(sender: barButton), @@ -132,9 +146,9 @@ final class GetAlbumsLinkViewModelTests: XCTestCase { ], itemHandle: $0.id) } let links = Dictionary(uniqueKeysWithValues: albums.map { ($0.id, "link-\($0.id)") }) - let sut = GetAlbumsLinkViewModel(albums: albums, - shareAlbumUseCase: MockShareAlbumUseCase(shareAlbumsLinks: links), - sectionViewModels: sections) + let sut = makeGetAlbumsLinkViewModel(albums: albums, + shareAlbumUseCase: MockShareAlbumUseCase(shareAlbumsLinks: links), + sectionViewModels: sections) let expectedRowReloads = sections.indices.map { IndexPath(row: 0, section: $0) @@ -160,9 +174,10 @@ final class GetAlbumsLinkViewModelTests: XCTestCase { } let expectedLink = "link-to-copy" let links = [album.id: expectedLink] - let sut = GetAlbumsLinkViewModel(albums: albums, - shareAlbumUseCase: MockShareAlbumUseCase(shareAlbumsLinks: links), - sectionViewModels: sections) + let sut = makeGetAlbumsLinkViewModel(albums: albums, + shareAlbumUseCase: MockShareAlbumUseCase(shareAlbumsLinks: links), + sectionViewModels: sections) + let linkIndexPath = IndexPath(row: 0, section: 0) expectSuccessfulOnViewReady(sut: sut, albums: albums, expectedRowReload: [linkIndexPath]) @@ -174,7 +189,9 @@ final class GetAlbumsLinkViewModelTests: XCTestCase { ]) } - private func expectSuccessfulOnViewReady(sut: GetAlbumsLinkViewModel, albums: [AlbumEntity], expectedRowReload: [IndexPath]) { + private func expectSuccessfulOnViewReady(sut: GetAlbumsLinkViewModel, + albums: [AlbumEntity], + expectedRowReload: [IndexPath]) { test(viewModel: sut, action: .onViewReady, expectedCommands: [ .configureView(title: Strings.Localizable.General.MenuAction.ShareLink.title(albums.count), isMultilink: true, @@ -186,4 +203,16 @@ final class GetAlbumsLinkViewModelTests: XCTestCase { .dismissHud ]) } + + private func makeGetAlbumsLinkViewModel( + albums: [AlbumEntity] = [], + shareAlbumUseCase: some ShareAlbumUseCaseProtocol = MockShareAlbumUseCase(), + sectionViewModels: [GetLinkSectionViewModel] = [], + tracker: some AnalyticsTracking = MockTracker() + ) -> GetAlbumsLinkViewModel { + GetAlbumsLinkViewModel(albums: albums, + shareAlbumUseCase: shareAlbumUseCase, + sectionViewModels: sectionViewModels, + tracker: tracker) + } } diff --git a/MEGAUnitTests/Home/Search/HomeSearchResultsProvidingTests.swift b/MEGAUnitTests/Home/Search/HomeSearchResultsProvidingTests.swift new file mode 100644 index 0000000000..a7b52e25d5 --- /dev/null +++ b/MEGAUnitTests/Home/Search/HomeSearchResultsProvidingTests.swift @@ -0,0 +1,102 @@ +@testable import MEGA +import MEGADomain +import MEGADomainMock +import Search +import XCTest + +class HomeSearchProvidingTests: XCTestCase { + func testHomeSearch_whenSuccess_shouldReturnResults() async throws { + let sut = makeSUT( + searchFileUseCase: MockSearchFileUseCase( + nodes: [ + .init(name: "node 0"), + .init(name: "node 1"), + .init(name: "node 2"), + .init(name: "node 10") + ] + ), + nodeDetailsUseCase: MockNodeDetailUseCase( + owner: .init(name: "owner"), + thumbnail: UIImage(systemName: "square.and.arrow.up") + ) + ) + + let expectedResults: [SearchResult] = [ + .init( + id: .init(stringLiteral: ""), + title: "node 1", + description: "owner", + properties: [], + thumbnailImageData: { UIImage(named: "square.and.arrow.up")?.pngData() ?? Data() }, + type: .node + ), + .init( + id: .init(stringLiteral: ""), + title: "node 10", + description: "owner", + properties: [], + thumbnailImageData: { UIImage(named: "square.and.arrow.up")?.pngData() ?? Data() }, + type: .node + ) + ] + + let searchResults = try await sut.search( + queryRequest: .init( + query: "node 1", + sorting: .automatic, + mode: .home, + chips: [] + ) + ) + + XCTAssertEqual(searchResults.results.count, 2) + + searchResults.results.enumerated().forEach { index, result in + XCTAssertEqual( + result.title, + expectedResults[index].title, + "Expect to match title, but failed at index: \(index)" + ) + XCTAssertEqual( + result.description, + expectedResults[index].description, + "Expect to match description, but failed at index: \(index)" + ) + } + + let thumbnail = await searchResults.results[0].thumbnailImageData() + XCTAssertEqual(thumbnail, UIImage(systemName: "square.and.arrow.up")?.pngData()) + } + + func testHomeSearch_whenFailures_shouldReturnNoResults() async throws { + let sut = makeSUT() + + let searchResults = try await sut.search( + queryRequest: .init( + query: "search tests", + sorting: .automatic, + mode: .home, + chips: [] + ) + ) + + XCTAssertEqual(searchResults.results.count, 0) + } + + private func makeSUT( + searchFileUseCase: MockSearchFileUseCase = .init(), + nodeDetailsUseCase: MockNodeDetailUseCase = .init(), + file: StaticString = #filePath, + line: UInt = #line + + ) -> HomeSearchResultsProviding { + let sut = HomeSearchResultsProviding( + searchFileUseCase: searchFileUseCase, + nodeDetailUseCase: nodeDetailsUseCase + ) + + trackForMemoryLeaks(on: sut, file: file, line: line) + + return sut + } +} diff --git a/MEGAUnitTests/Launching/DiskFullBlockingViewModelTests.swift b/MEGAUnitTests/Launching/DiskFullBlockingViewModelTests.swift index b171b443fb..43d6732fbc 100644 --- a/MEGAUnitTests/Launching/DiskFullBlockingViewModelTests.swift +++ b/MEGAUnitTests/Launching/DiskFullBlockingViewModelTests.swift @@ -1,4 +1,5 @@ @testable import MEGA +import MEGAL10n import XCTest final class DiskFullBlockingViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/MEGA.xctestplan b/MEGAUnitTests/MEGA.xctestplan index f5f3072676..0fcc8d47dd 100644 --- a/MEGAUnitTests/MEGA.xctestplan +++ b/MEGAUnitTests/MEGA.xctestplan @@ -125,6 +125,7 @@ { "parallelizable" : true, "skippedTests" : [ + "BannerContainerViewModelTests\/testAction_onViewWillAppear()", "ChatRoomsListViewModelTests\/testAction_selectChatsMode()", "ChatRoomsListViewModelTests\/testAction_selectMeetingsMode()", "DownloadLinkViewModelTests\/testDownloadFolderLinkUserNotLogged()", @@ -148,7 +149,9 @@ "MeetingFloatingPanelViewModelTests\/testAction_onViewReady_isMyselfParticipant_isOneToOneMeeting()", "MeetingParticipantsLayoutViewModelTests\/testAction_onViewReady()", "MeetingParticipantsLayoutViewModelTests\/testAction_participantAdded_createAvatar()", - "MeetingParticipantsLayoutViewModelTests\/testAction_participantAdded_downloadAvatar()" + "MeetingParticipantsLayoutViewModelTests\/testAction_participantAdded_downloadAvatar()", + "ScheduledMeetingDateBuilderTests", + "WaitingRoomViewModelTests\/testMeetingDate_givenMeetingStartAndEndDate_shouldMatch()" ], "target" : { "containerPath" : "container:MEGA.xcodeproj", @@ -158,6 +161,11 @@ }, { "parallelizable" : true, + "skippedTests" : [ + "UserAttributeUseCaseTest\/testSaveTimelineFilter_onFirstTime_shouldSaveCorrectly()", + "UserAttributeUseCaseTest\/testSaveTimelineFilter_onFirstTime_shouldUsePreferenceBeEmpty()", + "UserAttributeUseCaseTest\/testSaveTimelineFilter_onSecondTime_shouldSaveCorrectly()" + ], "target" : { "containerPath" : "container:Modules\/Domain\/MEGADomain", "identifier" : "MEGADomainTests", @@ -166,6 +174,9 @@ }, { "parallelizable" : true, + "skippedTests" : [ + "DateFormatter_System_ConvenientTests" + ], "target" : { "containerPath" : "container:Modules\/Infrastracture\/MEGAFoundation", "identifier" : "MEGAFoundationTests", @@ -252,11 +263,22 @@ }, { "parallelizable" : true, + "skippedTests" : [ + "DeviceListViewModelTests\/testFilterDevices_withSearchText_matchingDeviceName()" + ], "target" : { "containerPath" : "container:Modules\/Presentation\/Features\/DeviceCenter", "identifier" : "DeviceCenterTests", "name" : "DeviceCenterTests" } + }, + { + "enabled" : false, + "target" : { + "containerPath" : "container:Modules\/Features\/Search", + "identifier" : "SearchTests", + "name" : "SearchTests" + } } ], "version" : 1 diff --git a/MEGAUnitTests/Meeting/ChatRoomViewModelTests.swift b/MEGAUnitTests/Meeting/ChatRoomViewModelTests.swift index e5a1b3c508..8a5443a801 100644 --- a/MEGAUnitTests/Meeting/ChatRoomViewModelTests.swift +++ b/MEGAUnitTests/Meeting/ChatRoomViewModelTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import MEGAPermissions import MEGAPermissionsMock import XCTest diff --git a/MEGAUnitTests/Meeting/FutureMeetingRoomViewModelTests.swift b/MEGAUnitTests/Meeting/FutureMeetingRoomViewModelTests.swift index e0cfe5e477..d3809f9d1e 100644 --- a/MEGAUnitTests/Meeting/FutureMeetingRoomViewModelTests.swift +++ b/MEGAUnitTests/Meeting/FutureMeetingRoomViewModelTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class FutureMeetingRoomViewModelTests: XCTestCase { @@ -114,7 +115,7 @@ final class FutureMeetingRoomViewModelTests: XCTestCase { func testStartOrJoinCallActionTapped_startCall() { chatUseCase.isCallActive = false - callUseCase.callCompletion = .success(callUseCase.call) + callUseCase.callCompletion = .success(callUseCase.call ?? CallEntity()) let viewModel = FutureMeetingRoomViewModel(router: router, chatRoomUseCase: chatRoomUseCase, chatUseCase: chatUseCase, callUseCase: callUseCase) @@ -147,7 +148,7 @@ final class FutureMeetingRoomViewModelTests: XCTestCase { func testStartOrJoinCallActionTapped_joinCall() { chatUseCase.isCallActive = true - callUseCase.callCompletion = .success(callUseCase.call) + callUseCase.callCompletion = .success(callUseCase.call ?? CallEntity()) let viewModel = FutureMeetingRoomViewModel(router: router, chatRoomUseCase: chatRoomUseCase, chatUseCase: chatUseCase, callUseCase: callUseCase) diff --git a/MEGAUnitTests/Meeting/MeetingParticpiantInfoViewModelTests.swift b/MEGAUnitTests/Meeting/MeetingParticpiantInfoViewModelTests.swift index 56d712c929..4d47638156 100644 --- a/MEGAUnitTests/Meeting/MeetingParticpiantInfoViewModelTests.swift +++ b/MEGAUnitTests/Meeting/MeetingParticpiantInfoViewModelTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class MeetingParticpiantInfoViewModelTests: XCTestCase { @@ -17,7 +18,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: megaHandleUseCase, isMyselfModerator: true, router: router) @@ -44,7 +44,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: megaHandleUseCase, isMyselfModerator: true, router: router) @@ -71,7 +70,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: megaHandleUseCase, isMyselfModerator: true, router: router) @@ -99,7 +97,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: megaHandleUseCase, isMyselfModerator: true, router: router) @@ -127,7 +124,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: megaHandleUseCase, isMyselfModerator: false, router: router) @@ -155,7 +151,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: megaHandleUseCase, isMyselfModerator: false, router: router) @@ -182,7 +177,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: megaHandleUseCase, isMyselfModerator: true, router: router) @@ -209,7 +203,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: MockMEGAHandleUseCase(), isMyselfModerator: true, router: router) @@ -229,7 +222,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: MockChatRoomUserUseCase(), - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: MockMEGAHandleUseCase(), isMyselfModerator: true, router: router) @@ -238,46 +230,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { XCTAssert(router.openChatRoom_calledTimes == 1) } - func testAction_addToContact_success() { - let participant = CallParticipantEntity(chatId: 100, participantId: 100, clientId: 100, isModerator: false, isInContactList: false, canReceiveVideoHiRes: true) - let chatRoomUseCase = MockChatRoomUseCase() - let chatRoomUserUseCase = MockChatRoomUserUseCase(userDisplayNameForPeerResult: .success("Test")) - let userImageUseCase = MockUserImageUseCase(result: .success(UIImage())) - let router = MockMeetingParticpiantInfoViewRouter() - - let viewModel = MeetingParticpiantInfoViewModel(participant: participant, - userImageUseCase: userImageUseCase, - chatRoomUseCase: chatRoomUseCase, - chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), - megaHandleUseCase: MockMEGAHandleUseCase(), - isMyselfModerator: true, - router: router) - - viewModel.dispatch(.addToContact) - XCTAssert(router.showInviteSuccess_calledTimes == 1) - } - - func testAction_addToContact_error() { - let participant = CallParticipantEntity(chatId: 100, participantId: 100, clientId: 100, isModerator: false, isInContactList: false, canReceiveVideoHiRes: true) - let chatRoomUseCase = MockChatRoomUseCase() - let chatRoomUserUseCase = MockChatRoomUserUseCase(userDisplayNameForPeerResult: .success("Test")) - let userImageUseCase = MockUserImageUseCase(result: .success(UIImage())) - let router = MockMeetingParticpiantInfoViewRouter() - - let viewModel = MeetingParticpiantInfoViewModel(participant: participant, - userImageUseCase: userImageUseCase, - chatRoomUseCase: chatRoomUseCase, - chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .failure(.generic(""))), - megaHandleUseCase: MockMEGAHandleUseCase(), - isMyselfModerator: true, - router: router) - - viewModel.dispatch(.addToContact) - XCTAssert(router.showInviteErrorMessage_calledTimes == 1) - } - func testAction_makeModerator() { let participant = CallParticipantEntity(chatId: 100, participantId: 100, clientId: 100, isModerator: false, isInContactList: false, canReceiveVideoHiRes: true) let chatRoomUseCase = MockChatRoomUseCase() @@ -289,7 +241,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: MockMEGAHandleUseCase(), isMyselfModerator: true, router: router) @@ -309,7 +260,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: MockMEGAHandleUseCase(), isMyselfModerator: true, router: router) @@ -329,7 +279,6 @@ final class MeetingParticpiantInfoViewModelTests: XCTestCase { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: MockUserInviteUseCase(result: .success), megaHandleUseCase: MockMEGAHandleUseCase(), isMyselfModerator: true, router: router) @@ -408,14 +357,6 @@ final class MockMeetingParticpiantInfoViewRouter: MeetingParticpiantInfoViewRout openChatRoom_calledTimes += 1 } - func showInviteSuccess(email: String) { - showInviteSuccess_calledTimes += 1 - } - - func showInviteErrorMessage(_ message: String) { - showInviteErrorMessage_calledTimes += 1 - } - func makeParticipantAsModerator() { makeParticipantAsModerator_calledTimes += 1 } diff --git a/MEGAUnitTests/Meeting/Mocks/MockCallUseCase.swift b/MEGAUnitTests/Meeting/Mocks/MockCallUseCase.swift index 95aa194893..9b8cea8532 100644 --- a/MEGAUnitTests/Meeting/Mocks/MockCallUseCase.swift +++ b/MEGAUnitTests/Meeting/Mocks/MockCallUseCase.swift @@ -4,15 +4,20 @@ import MEGADomain final class MockCallUseCase: CallUseCaseProtocol { var startListeningForCall_CalledTimes = 0 var stopListeningForCall_CalledTimes = 0 - var callCompletion: Result = .failure(.generic) var createActiveSessions_calledTimes = 0 var hangCall_CalledTimes = 0 var endCall_CalledTimes = 0 var addPeer_CalledTimes = 0 var removePeer_CalledTimes = 0 + var allowUsersJoinCall_CalledTimes = 0 + var kickUsersFromCall_CalledTimes = 0 + var pushUsersIntoWaitingRoom_CalledTimes = 0 var makePeerAsModerator_CalledTimes = 0 var removePeerAsModerator_CalledTimes = 0 - var call: CallEntity + + var call: CallEntity? + var callCompletion: Result + var answerCallCompletion: Result var callbacksDelegate: (any CallCallbacksUseCaseProtocol)? var networkQuality: NetworkQuality = .bad @@ -22,10 +27,14 @@ final class MockCallUseCase: CallUseCaseProtocol { var chatSession: ChatSessionEntity? var participantHandle: HandleEntity = .invalid - init(call: CallEntity = CallEntity()) { + init(call: CallEntity? = CallEntity(), + callCompletion: Result = .failure(.generic), + answerCallCompletion: Result = .failure(.generic)) { self.call = call + self.callCompletion = callCompletion + self.answerCallCompletion = answerCallCompletion } - + func startListeningForCallInChat(_ chatId: HandleEntity, callbacksDelegate: T) { startListeningForCall_CalledTimes += 1 } @@ -39,7 +48,7 @@ final class MockCallUseCase: CallUseCaseProtocol { } func answerCall(for chatId: HandleEntity, completion: @escaping (Result) -> Void) { - completion(callCompletion) + completion(answerCallCompletion) } func startCall(for chatId: HandleEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) { @@ -68,6 +77,19 @@ final class MockCallUseCase: CallUseCaseProtocol { } } + func startMeetingInWaitingRoomChat(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) { + completion(callCompletion) + } + + func startMeetingInWaitingRoomChat(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool) async throws -> CallEntity { + switch callCompletion { + case .success(let callEntity): + return callEntity + case .failure(let failure): + throw failure + } + } + func joinCall(for chatId: HandleEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) { completion(callCompletion) } @@ -92,6 +114,18 @@ final class MockCallUseCase: CallUseCaseProtocol { removePeer_CalledTimes += 1 } + func allowUsersJoinCall(_ call: CallEntity, users: [HandleEntity]) { + allowUsersJoinCall_CalledTimes += 1 + } + + func kickUsersFromCall(_ call: CallEntity, users: [HandleEntity]) { + kickUsersFromCall_CalledTimes += 1 + } + + func pushUsersIntoWaitingRoom(for scheduledMeeting: ScheduledMeetingEntity, users: [HandleEntity]) { + pushUsersIntoWaitingRoom_CalledTimes += 1 + } + func makePeerAModerator(inCall call: CallEntity, peerId: UInt64) { makePeerAsModerator_CalledTimes += 1 } @@ -102,7 +136,7 @@ final class MockCallUseCase: CallUseCaseProtocol { } extension MockCallUseCase: CallCallbacksRepositoryProtocol { - + func createdSession(_ session: ChatSessionEntity, in chatId: HandleEntity) { guard let chatSession = chatSession, let chatRoom = chatRoom else { MEGALogDebug("Error getting mock properties") @@ -136,7 +170,7 @@ extension MockCallUseCase: CallCallbacksRepositoryProtocol { } func callTerminated(_ call: CallEntity) { - callbacksDelegate?.callTerminated(self.call) + callbacksDelegate?.callTerminated(call) } func ownPrivilegeChanged(to privilege: ChatRoomPrivilegeEntity, in chatRoom: ChatRoomEntity) { @@ -194,4 +228,12 @@ extension MockCallUseCase: CallCallbacksRepositoryProtocol { func outgoingRingingStopReceived() { callbacksDelegate?.outgoingRingingStopReceived() } + + func waitingRoomUsersEntered(with handles: [HandleEntity]) { + callbacksDelegate?.waitingRoomUsersEntered(with: handles) + } + + func waitingRoomUsersLeave(with handles: [HandleEntity]) { + callbacksDelegate?.waitingRoomUsersLeave(with: handles) + } } diff --git a/MEGAUnitTests/Meeting/Mocks/MockChatRoomUseCase.swift b/MEGAUnitTests/Meeting/Mocks/MockChatRoomUseCase.swift index 3398e5e6cf..6a237fe287 100644 --- a/MEGAUnitTests/Meeting/Mocks/MockChatRoomUseCase.swift +++ b/MEGAUnitTests/Meeting/Mocks/MockChatRoomUseCase.swift @@ -31,6 +31,7 @@ struct MockChatRoomUseCase: ChatRoomUseCaseProtocol { var chatSourceEntity: ChatSourceEntity = .error var chatMessageLoadedSubject = PassthroughSubject() var chatMessageScheduledMeetingChange: ChatMessageScheduledMeetingChangeType = .none + var shouldOpenWaitRoom: Bool = true func chatRoom(forUserHandle userHandle: UInt64) -> ChatRoomEntity? { return chatRoomEntity @@ -160,4 +161,8 @@ struct MockChatRoomUseCase: ChatRoomUseCaseProtocol { func hasScheduledMeetingChange(_ change: ChatMessageScheduledMeetingChangeType, for message: ChatMessageEntity, inChatRoom chatRoom: ChatRoomEntity) -> Bool { change == chatMessageScheduledMeetingChange } + + func shouldOpenWaitingRoom(forChatId chatId: HandleEntity) -> Bool { + shouldOpenWaitRoom + } } diff --git a/MEGAUnitTests/Meeting/Mocks/MockMeetingCreatingUseCase.swift b/MEGAUnitTests/Meeting/Mocks/MockMeetingCreatingUseCase.swift index 2f31cc3570..4fd4448543 100644 --- a/MEGAUnitTests/Meeting/Mocks/MockMeetingCreatingUseCase.swift +++ b/MEGAUnitTests/Meeting/Mocks/MockMeetingCreatingUseCase.swift @@ -4,14 +4,22 @@ import MEGADomain final class MockMeetingCreatingUseCase: MeetingCreatingUseCaseProtocol { let userName: String var chatCallCompletion: Result - var createEpehemeralAccountCompletion: Result? - var joinCallCompletion: Result = .failure(.generic) - + var createEpehemeralAccountCompletion: Result + var joinCallCompletion: Result + var joinChatCompletion: Result + var createChatLink_calledTimes = 0 - init(userName: String, chatCallCompletion: Result = .failure(.generic)) { + init(userName: String = "Test Name", + chatCallCompletion: Result = .failure(.generic), + createEpehemeralAccountCompletion: Result = .failure(.unexpected), + joinCallCompletion: Result = .failure(.generic), + joinChatCompletion: Result = .failure(.generic)) { self.userName = userName self.chatCallCompletion = chatCallCompletion + self.createEpehemeralAccountCompletion = createEpehemeralAccountCompletion + self.joinCallCompletion = joinCallCompletion + self.joinChatCompletion = joinChatCompletion } func startCall(_ startCall: StartCallEntity, completion: @escaping (Result) -> Void) { @@ -22,22 +30,24 @@ final class MockMeetingCreatingUseCase: MeetingCreatingUseCaseProtocol { completion(joinCallCompletion) } + func joinChat(forChatId chatId: UInt64, userHandle: UInt64, completion: @escaping (Result) -> Void) { + completion(joinChatCompletion) + } + func getUsername() -> String { userName } func getCall(forChatId chatId: UInt64) -> CallEntity? { - CallEntity(status: .inProgress, chatId: 0, callId: 0, changeTye: nil, duration: 0, initialTimestamp: 0, finalTimestamp: 0, hasLocalAudio: false, hasLocalVideo: false, termCodeType: nil, isRinging: false, callCompositionChange: nil, numberOfParticipants: 0, isOnHold: false, sessionClientIds: [], clientSessions: [], participants: [], uuid: UUID(uuidString: "45adcd56-a31c-11eb-bcbc-0242ac130002")!) + CallEntity(status: .inProgress, chatId: 0, callId: 0, changeType: nil, duration: 0, initialTimestamp: 0, finalTimestamp: 0, hasLocalAudio: false, hasLocalVideo: false, termCodeType: nil, isRinging: false, callCompositionChange: nil, numberOfParticipants: 0, isOnHold: false, sessionClientIds: [], clientSessions: [], participants: [], waitingRoomStatus: .unknown, waitingRoom: nil, waitingRoomHandleList: [], uuid: UUID(uuidString: "45adcd56-a31c-11eb-bcbc-0242ac130002")!) } func checkChatLink(link: String, completion: @escaping (Result) -> Void) { completion(chatCallCompletion) } - + func createEphemeralAccountAndJoinChat(firstName: String, lastName: String, link: String, completion: @escaping (Result) -> Void, karereInitCompletion: @escaping () -> Void) { - if let completionBlock = createEpehemeralAccountCompletion { - completion(completionBlock) - } + completion(createEpehemeralAccountCompletion) } func createChatLink(forChatId chatId: UInt64) { diff --git a/MEGAUnitTests/Meeting/Mocks/MockUserInviteUseCase.swift b/MEGAUnitTests/Meeting/Mocks/MockUserInviteUseCase.swift deleted file mode 100644 index bff0b7c663..0000000000 --- a/MEGAUnitTests/Meeting/Mocks/MockUserInviteUseCase.swift +++ /dev/null @@ -1,10 +0,0 @@ -@testable import MEGA -import MEGADomain - -struct MockUserInviteUseCase: UserInviteUseCaseProtocol { - var result: Result = .failure(.generic("")) - func sendInvite(forEmail email: String, - completion: @escaping (Result) -> Void) { - completion(result) - } -} diff --git a/MEGAUnitTests/Meeting/Mocks/WaitingRoomViewModel+Additions.swift b/MEGAUnitTests/Meeting/Mocks/WaitingRoomViewModel+Additions.swift index 2687bbabc5..e08d0617df 100644 --- a/MEGAUnitTests/Meeting/Mocks/WaitingRoomViewModel+Additions.swift +++ b/MEGAUnitTests/Meeting/Mocks/WaitingRoomViewModel+Additions.swift @@ -8,21 +8,44 @@ extension WaitingRoomViewModel { convenience init( scheduledMeeting: ScheduledMeetingEntity = ScheduledMeetingEntity(), router: some WaitingRoomViewRouting = MockWaitingRoomViewRouter(), + chatUseCase: some ChatUseCaseProtocol = MockChatUseCase(), + callUseCase: some CallUseCaseProtocol = MockCallUseCase(), + callCoordinatorUseCase: some CallCoordinatorUseCaseProtocol = MockCallCoordinatorUseCase(), + meetingUseCase: some MeetingCreatingUseCaseProtocol = MockMeetingCreatingUseCase(), + authUseCase: some AuthUseCaseProtocol = MockAuthUseCase(), + waitingRoomUseCase: some WaitingRoomUseCaseProtocol = MockWaitingRoomUseCase(), accountUseCase: some AccountUseCaseProtocol = MockAccountUseCase(), + megaHandleUseCase: some MEGAHandleUseCaseProtocol = MockMEGAHandleUseCase(), userImageUseCase: some UserImageUseCaseProtocol = MockUserImageUseCase(), localVideoUseCase: some CallLocalVideoUseCaseProtocol = MockCallLocalVideoUseCase(), + captureDeviceUseCase: some CaptureDeviceUseCaseProtocol = MockCaptureDeviceUseCase(), audioSessionUseCase: some AudioSessionUseCaseProtocol = MockAudioSessionUseCase(), - permissionHandler: some DevicePermissionsHandling = MockDevicePermissionHandler(), + permissionHandler: some DevicePermissionsHandling = MockDevicePermissionHandler + .init( + photoAuthorization: .authorized, + audioAuthorized: true, + videoAuthorized: true + ), + chatLink: String? = nil, isTesting: Bool = true ) { self.init( scheduledMeeting: scheduledMeeting, router: router, + chatUseCase: chatUseCase, + callUseCase: callUseCase, + callCoordinatorUseCase: callCoordinatorUseCase, + meetingUseCase: meetingUseCase, + authUseCase: authUseCase, + waitingRoomUseCase: waitingRoomUseCase, accountUseCase: accountUseCase, + megaHandleUseCase: megaHandleUseCase, userImageUseCase: userImageUseCase, localVideoUseCase: localVideoUseCase, + captureDeviceUseCase: captureDeviceUseCase, audioSessionUseCase: audioSessionUseCase, - permissionHandler: permissionHandler + permissionHandler: permissionHandler, + chatLink: chatLink ) } } diff --git a/MEGAUnitTests/Meeting/ScheduleMeetingCreationIntervalFooterNoteTests.swift b/MEGAUnitTests/Meeting/ScheduleMeetingCreationIntervalFooterNoteTests.swift index 530d68353f..6bb6f27960 100644 --- a/MEGAUnitTests/Meeting/ScheduleMeetingCreationIntervalFooterNoteTests.swift +++ b/MEGAUnitTests/Meeting/ScheduleMeetingCreationIntervalFooterNoteTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class ScheduleMeetingCreationIntervalFooterNoteTests: XCTestCase { diff --git a/MEGAUnitTests/Meeting/ScheduleMeetingCreationMonthlyCustomOptionsViewModelTests.swift b/MEGAUnitTests/Meeting/ScheduleMeetingCreationMonthlyCustomOptionsViewModelTests.swift index 92026e1b5d..c39b147e4e 100644 --- a/MEGAUnitTests/Meeting/ScheduleMeetingCreationMonthlyCustomOptionsViewModelTests.swift +++ b/MEGAUnitTests/Meeting/ScheduleMeetingCreationMonthlyCustomOptionsViewModelTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class ScheduleMeetingCreationMonthlyCustomOptionsViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/Meeting/ScheduleMeetingSelectedFrequencyDetailsTests.swift b/MEGAUnitTests/Meeting/ScheduleMeetingSelectedFrequencyDetailsTests.swift index a1fb056c3d..842356b0e7 100644 --- a/MEGAUnitTests/Meeting/ScheduleMeetingSelectedFrequencyDetailsTests.swift +++ b/MEGAUnitTests/Meeting/ScheduleMeetingSelectedFrequencyDetailsTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class ScheduleMeetingSelectedFrequencyDetailsTests: XCTestCase { diff --git a/MEGAUnitTests/Meeting/ScheduleMeetingViewModelTests.swift b/MEGAUnitTests/Meeting/ScheduleMeetingViewModelTests.swift index 4d53727f8d..76fd107056 100644 --- a/MEGAUnitTests/Meeting/ScheduleMeetingViewModelTests.swift +++ b/MEGAUnitTests/Meeting/ScheduleMeetingViewModelTests.swift @@ -2,6 +2,7 @@ import Combine @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class ScheduleMeetingViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/Meeting/WaitingRoomViewModelTests.swift b/MEGAUnitTests/Meeting/WaitingRoomViewModelTests.swift index 11ec3b4bed..9dfbc5ef2b 100644 --- a/MEGAUnitTests/Meeting/WaitingRoomViewModelTests.swift +++ b/MEGAUnitTests/Meeting/WaitingRoomViewModelTests.swift @@ -1,6 +1,8 @@ +import Combine @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class WaitingRoomViewModelTests: XCTestCase { @@ -12,27 +14,275 @@ final class WaitingRoomViewModelTests: XCTestCase { XCTAssertEqual(sut.meetingTitle, meetingTitle) } + func testMeetingDate_givenMeetingStartAndEndDate_shouldMatch() throws { + let startDate = try XCTUnwrap(sampleDate(from: "21/09/2023 10:30")) + let endDate = try XCTUnwrap(sampleDate(from: "21/09/2023 10:45")) + let scheduledMeeting = ScheduledMeetingEntity(startDate: startDate, endDate: endDate) + let sut = WaitingRoomViewModel(scheduledMeeting: scheduledMeeting) + + XCTAssertEqual(sut.createMeetingDate(), "Thu, 21 Sep ·10:30-10:45") + } + func testViewState_onLoadWaitingRoomAndIsGuest_shouldBeGuestJoinState() { let accountUseCase = MockAccountUseCase(isGuest: true) let sut = WaitingRoomViewModel(accountUseCase: accountUseCase) XCTAssertEqual(sut.viewState, .guestJoin) } - func testViewState_onLoadWaitingRoomAndIsNotGuest_shouldBeWaitForHostToLetInJoinState() { + func testViewState_onLoadWaitingRoomAndIsNotGuestAndMeetingNotStart_shouldBeWaitForHostToStartState() { + let callUseCase = MockCallUseCase(call: nil) + let sut = WaitingRoomViewModel(callUseCase: callUseCase) + XCTAssertEqual(sut.viewState, .waitForHostToStart) + } + + func testViewState_onLoadWaitingRoomAndIsNotGuestAndMeetingDidStart_shouldBeWaitForHostToStartState() { let sut = WaitingRoomViewModel() XCTAssertEqual(sut.viewState, .waitForHostToLetIn) } + + func testViewState_onMeetingNotStartTransitsToMeetingDidStart_shouldChangeFromWaitForHostToStartToWaitForHostToLetIn() { + let scheduledMeeting = ScheduledMeetingEntity(chatId: 100) + let chatCallStatusUpdatePublisher = PassthroughSubject() + let chatUseCase = MockChatUseCase(chatCallStatusUpdatePublisher: chatCallStatusUpdatePublisher) + let callEntity = CallEntity(status: .connecting, chatId: 100) + let callUseCase = MockCallUseCase(call: nil, answerCallCompletion: .success(callEntity)) + let sut = WaitingRoomViewModel(scheduledMeeting: scheduledMeeting, chatUseCase: chatUseCase, callUseCase: callUseCase) + + XCTAssertEqual(sut.viewState, .waitForHostToStart) + + callUseCase.call = callEntity + chatCallStatusUpdatePublisher.send(callEntity) + + evaluate { + sut.viewState == .waitForHostToLetIn + } + } + + func testViewState_onMeetingDidStartTransitsToMeetingNotStart_shouldChangeFromWaitForHostToLetInToWaitForHostToStart() { + let scheduledMeeting = ScheduledMeetingEntity(chatId: 100) + let chatCallStatusUpdatePublisher = PassthroughSubject() + let chatUseCase = MockChatUseCase(isCallActive: true, chatCallStatusUpdatePublisher: chatCallStatusUpdatePublisher) + let callUseCase = MockCallUseCase() + let sut = WaitingRoomViewModel(scheduledMeeting: scheduledMeeting, chatUseCase: chatUseCase, callUseCase: callUseCase) + + XCTAssertEqual(sut.viewState, .waitForHostToLetIn) + + callUseCase.call = nil + chatCallStatusUpdatePublisher.send(CallEntity(status: .terminatingUserParticipation, chatId: 100)) + + evaluate { + sut.viewState == .waitForHostToStart + } + } + + func testSpeakerButton_onTapSpeakerButton_shouldDisableSpeakerButton() { + let audioSessionUseCase = MockAudioSessionUseCase() + let sut = WaitingRoomViewModel(audioSessionUseCase: audioSessionUseCase) + + sut.enableLoudSpeaker(enabled: false) + + XCTAssertEqual(audioSessionUseCase.disableLoudSpeaker_calledTimes, 1) + } + + func testLeaveButton_didTapLeaveButton_shouldPresentLeaveAlert() { + let router = MockWaitingRoomViewRouter() + let sut = WaitingRoomViewModel(router: router) + + sut.leaveButtonTapped() + + XCTAssertEqual(router.showLeaveAlert_calledTimes, 1) + } + + func testMeetingInfoButton_didTapMeetingInfoButton_shouldPresentMeetingInfo() { + let router = MockWaitingRoomViewRouter() + let sut = WaitingRoomViewModel(router: router) + + sut.infoButtonTapped() + + XCTAssertEqual(router.showMeetingInfo_calledTimes, 1) + } + + func testCalculateVideoSize_portraitMode_shouldMatch() { + let screenHeight = 424.0 + let screenWidth = 236.0 + let sut = WaitingRoomViewModel() + sut.screenSize = CGSize(width: screenWidth, height: screenHeight) + + let videoSize = sut.calculateVideoSize() + + XCTAssertEqual(videoSize, calculateVideoSize(by: screenHeight, isLandscape: false)) + } + + func testCalculateVideoSize_landscapeMode_shouldMatch() { + let screenHeight = 236.0 + let screenWidth = 424.0 + let sut = WaitingRoomViewModel() + sut.screenSize = CGSize(width: screenWidth, height: screenHeight) + + let videoSize = sut.calculateVideoSize() + + XCTAssertEqual(videoSize, calculateVideoSize(by: screenHeight, isLandscape: true)) + } + + func testCalculateBottomPanelHeight_portraitModeAndGuestJoin_shouldMatch() { + let accountUseCase = MockAccountUseCase(isGuest: true) + let sut = WaitingRoomViewModel(accountUseCase: accountUseCase) + + XCTAssertEqual(sut.calculateBottomPanelHeight(), 142.0) + } + + func testCalculateBottomPanelHeight_portraitModeAndWaitForHostToLetIn_shouldMatch() { + let sut = WaitingRoomViewModel() + + XCTAssertEqual(sut.calculateBottomPanelHeight(), 100.0) + } + + func testCalculateBottomPanelHeight_landscapeModeAndGuestJoin_shouldMatch() { + let screenHeight = 236.0 + let screenWidth = 424.0 + let accountUseCase = MockAccountUseCase(isGuest: true) + let sut = WaitingRoomViewModel(accountUseCase: accountUseCase) + sut.screenSize = CGSize(width: screenWidth, height: screenHeight) + + XCTAssertEqual(sut.calculateBottomPanelHeight(), 142.0) + } + + func testCalculateBottomPanelHeight_landscapeModeAndWaitForHostToLetIn_shouldMatch() { + let screenHeight = 236.0 + let screenWidth = 424.0 + let sut = WaitingRoomViewModel() + sut.screenSize = CGSize(width: screenWidth, height: screenHeight) + + XCTAssertEqual(sut.calculateBottomPanelHeight(), 8.0) + } + + func testShowWaitingRoomMessage_whenGuestLogin_shouldNotShow() { + let accountUseCase = MockAccountUseCase(isGuest: true) + let sut = WaitingRoomViewModel(accountUseCase: accountUseCase) + + XCTAssertFalse(sut.showWaitingRoomMessage) + } + + func testShowWaitingRoomMessage_whenWaitForHostToStart_shouldShow() { + let chatUseCase = MockChatUseCase(isCallActive: false) + let sut = WaitingRoomViewModel(chatUseCase: chatUseCase) + + XCTAssertTrue(sut.showWaitingRoomMessage) + } + + func testShowWaitingRoomMessage_whenWaitForHostToLetIn_shouldShow() { + let chatUseCase = MockChatUseCase(isCallActive: true) + let sut = WaitingRoomViewModel(chatUseCase: chatUseCase) + + XCTAssertTrue(sut.showWaitingRoomMessage) + } + + func testWaitingRoomMessage_whenWaitForHostToStart_shouldMatch() { + let callUseCase = MockCallUseCase(call: nil) + let sut = WaitingRoomViewModel(callUseCase: callUseCase) + + XCTAssertEqual(sut.waitingRoomMessage, Strings.Localizable.Meetings.WaitingRoom.Message.waitForHostToStartTheMeeting) + } + + func testWaitingRoomMessage_whenWaitForHostToLetIn_shouldMatch() { + let sut = WaitingRoomViewModel() + + XCTAssertEqual(sut.waitingRoomMessage, Strings.Localizable.Meetings.WaitingRoom.Message.waitForHostToLetYouIn) + } + + func testTapJoinAction_onCreateEphemeralAccountSuccessAndJoinChatSuccessAndMeetingDidStart_shoudBecomeWaitForHostToLetIn() { + let callUseCase = MockCallUseCase(call: CallEntity(), answerCallCompletion: .success(CallEntity())) + let meetingUseCase = MockMeetingCreatingUseCase(createEpehemeralAccountCompletion: .success, joinChatCompletion: .success(ChatRoomEntity())) + let accountUseCase = MockAccountUseCase(isGuest: true) + let sut = WaitingRoomViewModel(callUseCase: callUseCase, + meetingUseCase: meetingUseCase, + accountUseCase: accountUseCase, + chatLink: "Test chatLink") + + XCTAssertEqual(sut.viewState, .guestJoin) + + sut.tapJoinAction(firstName: "First", lastName: "Last") + + evaluate { + sut.viewState == .waitForHostToLetIn + } + } + + func testTapJoinAction_onCreateEphemeralAccountSuccessAndJoinChatSuccessAndMeetingNotStart_shoudBecomeWaitForHostToStart() { + let callUseCase = MockCallUseCase(call: nil, answerCallCompletion: .success(CallEntity())) + let meetingUseCase = MockMeetingCreatingUseCase(createEpehemeralAccountCompletion: .success, joinChatCompletion: .success(ChatRoomEntity())) + let accountUseCase = MockAccountUseCase(isGuest: true) + let sut = WaitingRoomViewModel(callUseCase: callUseCase, + meetingUseCase: meetingUseCase, + accountUseCase: accountUseCase, + chatLink: "Test chatLink") + + XCTAssertEqual(sut.viewState, .guestJoin) + + sut.tapJoinAction(firstName: "First", lastName: "Last") + + evaluate { + sut.viewState == .waitForHostToStart + } + } + + // MARK: - Private methods + + private func sampleDate(from string: String = "12/06/2023 09:10") -> Date? { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd/MM/yyyy HH:mm" + return dateFormatter.date(from: string) + } + + private func calculateVideoSize(by screenHeight: CGFloat, isLandscape: Bool) -> CGSize { + let videoAspectRatio = isLandscape ? 424.0 / 236.0 : 236.0 / 424.0 + let videoHeight = screenHeight - (isLandscape ? 66.0 : 332.0) + let videoWidth = videoHeight * videoAspectRatio + return CGSize(width: videoWidth, height: videoHeight) + } + + private func evaluate(isInverted: Bool = false, expression: @escaping () -> Bool) { + let predicate = NSPredicate { _, _ in expression() } + let expectation = expectation(for: predicate, evaluatedWith: nil) + expectation.isInverted = isInverted + wait(for: [expectation], timeout: 5) + } } final class MockWaitingRoomViewRouter: WaitingRoomViewRouting { var dismiss_calledTimes = 0 var showLeaveAlert_calledTimes = 0 + var showMeetingInfo_calledTimes = 0 + var showVideoPermissionError_calledTimes = 0 + var showAudioPermissionError_calledTimes = 0 + var showHostDenyAlert_calledTimes = 0 + var hostAllowToJoin_calledTimes = 0 - func dismiss() { + func dismiss(completion: (() -> Void)?) { dismiss_calledTimes += 1 } func showLeaveAlert(leaveAction: @escaping () -> Void) { showLeaveAlert_calledTimes += 1 } + + func showMeetingInfo() { + showMeetingInfo_calledTimes += 1 + } + + func showVideoPermissionError() { + showVideoPermissionError_calledTimes += 1 + } + + func showAudioPermissionError() { + showAudioPermissionError_calledTimes += 1 + } + + func showHostDenyAlert(leaveAction: @escaping () -> Void) { + showHostDenyAlert_calledTimes += 1 + } + + func hostAllowToJoin() { + hostAllowToJoin_calledTimes += 1 + } } diff --git a/MEGAUnitTests/Mocks/MockTransferWidgetResponder.swift b/MEGAUnitTests/Mocks/MockTransferWidgetResponder.swift new file mode 100644 index 0000000000..c220332b54 --- /dev/null +++ b/MEGAUnitTests/Mocks/MockTransferWidgetResponder.swift @@ -0,0 +1,26 @@ +import Foundation +@testable import MEGA + +class MockTransferWidgetResponder: TransferWidgetResponderProtocol { + + var bringProgressToFrontKeyWindowIfNeededCalled: Int = 0 + var setProgressViewInKeyWindowCalled: Int = 0 + var updateProgressViewCalled: Int = 0 + var showWidgetIfNeededCalled: Int = 0 + + func bringProgressToFrontKeyWindowIfNeeded() { + bringProgressToFrontKeyWindowIfNeededCalled += 1 + } + + func setProgressViewInKeyWindow() { + setProgressViewInKeyWindowCalled += 1 + } + + func updateProgressView(bottomConstant: CGFloat) { + updateProgressViewCalled += 1 + } + + func showWidgetIfNeeded() { + showWidgetIfNeededCalled += 1 + } +} diff --git a/MEGAUnitTests/NameCollision/ActionViewModelTests.swift b/MEGAUnitTests/NameCollision/ActionViewModelTests.swift index 68223cc478..c4b1281bf6 100644 --- a/MEGAUnitTests/NameCollision/ActionViewModelTests.swift +++ b/MEGAUnitTests/NameCollision/ActionViewModelTests.swift @@ -1,4 +1,5 @@ @testable import MEGA +import MEGAL10n import XCTest class ActionViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/NameCollision/HeaderViewModelTests.swift b/MEGAUnitTests/NameCollision/HeaderViewModelTests.swift index abdd94b808..d330071eff 100644 --- a/MEGAUnitTests/NameCollision/HeaderViewModelTests.swift +++ b/MEGAUnitTests/NameCollision/HeaderViewModelTests.swift @@ -1,4 +1,5 @@ @testable import MEGA +import MEGAL10n import XCTest class HeaderViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/Permissions/AlertModel+Equatable.swift b/MEGAUnitTests/Permissions/AlertRouter/AlertModel+Equatable.swift similarity index 100% rename from MEGAUnitTests/Permissions/AlertModel+Equatable.swift rename to MEGAUnitTests/Permissions/AlertRouter/AlertModel+Equatable.swift diff --git a/MEGAUnitTests/Permissions/CustomModalModel+Equatable.swift b/MEGAUnitTests/Permissions/AlertRouter/CustomModalModel+Equatable.swift similarity index 100% rename from MEGAUnitTests/Permissions/CustomModalModel+Equatable.swift rename to MEGAUnitTests/Permissions/AlertRouter/CustomModalModel+Equatable.swift diff --git a/MEGAUnitTests/Permissions/PermissionAlertRouterTests.swift b/MEGAUnitTests/Permissions/AlertRouter/PermissionAlertRouterTests.swift similarity index 100% rename from MEGAUnitTests/Permissions/PermissionAlertRouterTests.swift rename to MEGAUnitTests/Permissions/AlertRouter/PermissionAlertRouterTests.swift diff --git a/MEGAUnitTests/Permissions/AlertViewModifier/PermissionAlertModelTests.swift b/MEGAUnitTests/Permissions/AlertViewModifier/PermissionAlertModelTests.swift new file mode 100644 index 0000000000..21efaba7cb --- /dev/null +++ b/MEGAUnitTests/Permissions/AlertViewModifier/PermissionAlertModelTests.swift @@ -0,0 +1,40 @@ +import Foundation +@testable import MEGA +import MEGAL10n +import XCTest + +class PermissionAlertModelTests: XCTestCase { + + func testPhotos_SecondaryAction_shouldTriggerCompletion() { + // Arrange + var secondaryActionCalled = 0 + let sut = PermissionAlertModel.photo(completion: { + secondaryActionCalled += 1 + }) + + // Act + sut.secondaryAction?.handler?() + + // Assert + XCTAssertEqual(sut.title, Strings.Localizable.attention) + XCTAssertEqual(sut.message, Strings.Localizable.photoLibraryPermissions) + XCTAssertEqual(secondaryActionCalled, 1) + + } + + func testVideos_SecondaryAction_shouldTriggerCompletion() { + // Arrange + var secondaryActionCalled = 0 + let sut = PermissionAlertModel.video(completion: { + secondaryActionCalled += 1 + }) + + // Act + sut.secondaryAction?.handler?() + + // Assert + XCTAssertEqual(sut.title, Strings.Localizable.attention) + XCTAssertEqual(sut.message, Strings.Localizable.cameraPermissions) + XCTAssertEqual(secondaryActionCalled, 1) + } +} diff --git a/MEGAUnitTests/Plan/UpgradeAccountPlanViewModelTests.swift b/MEGAUnitTests/Plan/UpgradeAccountPlanViewModelTests.swift index 6f8b74c5ef..b93f717945 100644 --- a/MEGAUnitTests/Plan/UpgradeAccountPlanViewModelTests.swift +++ b/MEGAUnitTests/Plan/UpgradeAccountPlanViewModelTests.swift @@ -8,11 +8,11 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { private var subscriptions = Set() private let freePlan = AccountPlanEntity(type: .free, name: "Free") - private let proI_monthly = AccountPlanEntity(type: .proI, name: "Pro I", term: .monthly) - private let proI_yearly = AccountPlanEntity(type: .proI, name: "Pro I", term: .yearly) - private let proII_monthly = AccountPlanEntity(type: .proII, name: "Pro II", term: .monthly) - private let proII_yearly = AccountPlanEntity(type: .proII, name: "Pro II", term: .yearly) - private let proIII_monthly = AccountPlanEntity(type: .proIII, name: "Pro III", term: .monthly) + private let proI_monthly = AccountPlanEntity(type: .proI, name: "Pro I", subscriptionCycle: .monthly) + private let proI_yearly = AccountPlanEntity(type: .proI, name: "Pro I", subscriptionCycle: .yearly) + private let proII_monthly = AccountPlanEntity(type: .proII, name: "Pro II", subscriptionCycle: .monthly) + private let proII_yearly = AccountPlanEntity(type: .proII, name: "Pro II", subscriptionCycle: .yearly) + private let proIII_monthly = AccountPlanEntity(type: .proIII, name: "Pro III", subscriptionCycle: .monthly) // MARK: - Init func testInit_registerDelegates_shouldRegisterDelegates() async { @@ -31,7 +31,7 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { let sut = UpgradeAccountPlanViewModel(accountDetails: details, purchaseUseCase: mockUseCase) await sut.setUpPlanTask?.value - XCTAssertEqual(sut.selectedTermTab, .yearly) + XCTAssertEqual(sut.selectedCycleTab, .yearly) XCTAssertEqual(sut.filteredPlanList, [proI_yearly]) XCTAssertEqual(sut.currentPlan?.type, .free) XCTAssertEqual(sut.recommendedPlanType, .proI) @@ -44,7 +44,7 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { let sut = UpgradeAccountPlanViewModel(accountDetails: details, purchaseUseCase: mockUseCase) await sut.setUpPlanTask?.value - XCTAssertEqual(sut.selectedTermTab, .monthly) + XCTAssertEqual(sut.selectedCycleTab, .monthly) XCTAssertEqual(sut.filteredPlanList, [proI_monthly, proII_monthly]) XCTAssertEqual(sut.currentPlan?.type, .proI) XCTAssertEqual(sut.recommendedPlanType, .proII) @@ -57,7 +57,7 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { let sut = UpgradeAccountPlanViewModel(accountDetails: details, purchaseUseCase: mockUseCase) await sut.setUpPlanTask?.value - XCTAssertEqual(sut.selectedTermTab, .yearly) + XCTAssertEqual(sut.selectedCycleTab, .yearly) XCTAssertEqual(sut.filteredPlanList, [proI_yearly, proII_yearly]) XCTAssertEqual(sut.currentPlan?.type, .proI) XCTAssertEqual(sut.recommendedPlanType, .proII) @@ -70,7 +70,7 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { let sut = UpgradeAccountPlanViewModel(accountDetails: details, purchaseUseCase: mockUseCase) await sut.setUpPlanTask?.value - XCTAssertEqual(sut.selectedTermTab, .yearly) + XCTAssertEqual(sut.selectedCycleTab, .yearly) XCTAssertEqual(sut.filteredPlanList, [proI_yearly, proII_yearly]) XCTAssertEqual(sut.currentPlan?.type, .proI) XCTAssertEqual(sut.recommendedPlanType, .proII) @@ -305,80 +305,80 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { } // MARK: - Selected term tab - func testSelectedTermTab_freeAccount_defaultShouldBeYearly() { + func testSelectedCycleTab_freeAccount_defaultShouldBeYearly() { let details = AccountDetailsEntity(proLevel: .free) let mockUseCase = MockAccountPlanPurchaseUseCase() let sut = UpgradeAccountPlanViewModel(accountDetails: details, purchaseUseCase: mockUseCase) - XCTAssertEqual(sut.selectedTermTab, .yearly) + XCTAssertEqual(sut.selectedCycleTab, .yearly) } - func testSelectedTermTab_oneTimePurchaseMonthly_defaultShouldBeYearly() { + func testSelectedCycleTab_oneTimePurchaseMonthly_defaultShouldBeYearly() { let details = AccountDetailsEntity(proLevel: .proI, subscriptionCycle: .none) let planList = [proI_monthly, proII_yearly] let mockUseCase = MockAccountPlanPurchaseUseCase(accountPlanProducts: planList) let exp = expectation(description: "Setting Plan Term Tab") let sut = UpgradeAccountPlanViewModel(accountDetails: details, purchaseUseCase: mockUseCase) - sut.$selectedTermTab + sut.$selectedCycleTab .dropFirst() .sink { _ in exp.fulfill() }.store(in: &subscriptions) wait(for: [exp], timeout: 0.5) - XCTAssertEqual(sut.selectedTermTab, .yearly) + XCTAssertEqual(sut.selectedCycleTab, .yearly) } - func testSelectedTermTab_oneTimePurchaseYearly_defaultShouldBeYearly() { + func testSelectedCycleTab_oneTimePurchaseYearly_defaultShouldBeYearly() { let details = AccountDetailsEntity(proLevel: .proI, subscriptionCycle: .none) let planList = [proI_monthly, proII_yearly] let mockUseCase = MockAccountPlanPurchaseUseCase(accountPlanProducts: planList) let exp = expectation(description: "Setting Plan Term Tab") let sut = UpgradeAccountPlanViewModel(accountDetails: details, purchaseUseCase: mockUseCase) - sut.$selectedTermTab + sut.$selectedCycleTab .dropFirst() .sink { _ in exp.fulfill() }.store(in: &subscriptions) wait(for: [exp], timeout: 0.5) - XCTAssertEqual(sut.selectedTermTab, .yearly) + XCTAssertEqual(sut.selectedCycleTab, .yearly) } - func testSelectedTermTab_recurringPlanYearly_defaultShouldBeYearly() { + func testSelectedCycleTab_recurringPlanYearly_defaultShouldBeYearly() { let details = AccountDetailsEntity(proLevel: .proI, subscriptionCycle: .yearly) let planList = [proI_monthly, proII_yearly] let mockUseCase = MockAccountPlanPurchaseUseCase(accountPlanProducts: planList) let exp = expectation(description: "Setting Plan Term Tab") let sut = UpgradeAccountPlanViewModel(accountDetails: details, purchaseUseCase: mockUseCase) - sut.$selectedTermTab + sut.$selectedCycleTab .dropFirst() .sink { _ in exp.fulfill() }.store(in: &subscriptions) wait(for: [exp], timeout: 0.5) - XCTAssertEqual(sut.selectedTermTab, .yearly) + XCTAssertEqual(sut.selectedCycleTab, .yearly) } - func testSelectedTermTab_recurringPlanMonthly_defaultShouldBeMonthly() { + func testSelectedCycleTab_recurringPlanMonthly_defaultShouldBeMonthly() { let details = AccountDetailsEntity(proLevel: .proI, subscriptionCycle: .monthly) let planList = [proI_monthly, proII_yearly] let mockUseCase = MockAccountPlanPurchaseUseCase(accountPlanProducts: planList) let exp = expectation(description: "Setting Plan Term Tab") let sut = UpgradeAccountPlanViewModel(accountDetails: details, purchaseUseCase: mockUseCase) - sut.$selectedTermTab + sut.$selectedCycleTab .dropFirst() .sink { _ in exp.fulfill() }.store(in: &subscriptions) wait(for: [exp], timeout: 0.5) - XCTAssertEqual(sut.selectedTermTab, .monthly) + XCTAssertEqual(sut.selectedCycleTab, .monthly) } // MARK: - Buy button @@ -415,7 +415,7 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { wait(for: [exp], timeout: 0.5) sut.setSelectedPlan(proII_monthly) - sut.selectedTermTab = .yearly + sut.selectedCycleTab = .yearly XCTAssertTrue(sut.isShowBuyButton) } @@ -434,7 +434,7 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { wait(for: [exp], timeout: 0.5) sut.setSelectedPlan(proII_yearly) - sut.selectedTermTab = .monthly + sut.selectedCycleTab = .monthly XCTAssertTrue(sut.isShowBuyButton) } @@ -446,11 +446,11 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { await sut.setUpPlanTask?.value - sut.selectedTermTab = .yearly + sut.selectedCycleTab = .yearly sut.setSelectedPlan(proI_yearly) XCTAssertTrue(sut.isShowBuyButton) - sut.selectedTermTab = .monthly + sut.selectedCycleTab = .monthly XCTAssertFalse(sut.isShowBuyButton) } @@ -462,11 +462,11 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { await sut.setUpPlanTask?.value - sut.selectedTermTab = .monthly + sut.selectedCycleTab = .monthly sut.setSelectedPlan(proI_monthly) XCTAssertTrue(sut.isShowBuyButton) - sut.selectedTermTab = .yearly + sut.selectedCycleTab = .yearly XCTAssertFalse(sut.isShowBuyButton) } @@ -485,7 +485,7 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { }.store(in: &subscriptions) wait(for: [exp], timeout: 0.5) - sut.selectedTermTab = .monthly + sut.selectedCycleTab = .monthly XCTAssertEqual(sut.filteredPlanList, [proI_monthly, proII_monthly]) } @@ -503,7 +503,7 @@ final class UpgradeAccountPlanViewModelTests: XCTestCase { }.store(in: &subscriptions) wait(for: [exp], timeout: 0.5) - sut.selectedTermTab = .yearly + sut.selectedCycleTab = .yearly XCTAssertEqual(sut.filteredPlanList, [proI_yearly, proII_yearly]) } diff --git a/MEGAUnitTests/Plan/UpgradeAccountPlanViewModel_createAccountPlanViewModelTests.swift b/MEGAUnitTests/Plan/UpgradeAccountPlanViewModel_createAccountPlanViewModelTests.swift index 9acf0f0fbb..d41c807ad1 100644 --- a/MEGAUnitTests/Plan/UpgradeAccountPlanViewModel_createAccountPlanViewModelTests.swift +++ b/MEGAUnitTests/Plan/UpgradeAccountPlanViewModel_createAccountPlanViewModelTests.swift @@ -7,12 +7,12 @@ import XCTest final class UpgradeAccountPlanViewModel_createAccountPlanViewModelTests: XCTestCase { private var subscriptions = Set() - private let proI_monthly = AccountPlanEntity(type: .proI, name: "Pro I", term: .monthly) - private let proI_yearly = AccountPlanEntity(type: .proI, name: "Pro I", term: .yearly) - private let proII_monthly = AccountPlanEntity(type: .proII, name: "Pro II", term: .monthly) - private let proII_yearly = AccountPlanEntity(type: .proII, name: "Pro II", term: .yearly) - private let proIII_monthly = AccountPlanEntity(type: .proII, name: "Pro III", term: .monthly) - private let proIII_yearly = AccountPlanEntity(type: .proII, name: "Pro III", term: .yearly) + private let proI_monthly = AccountPlanEntity(type: .proI, name: "Pro I", subscriptionCycle: .monthly) + private let proI_yearly = AccountPlanEntity(type: .proI, name: "Pro I", subscriptionCycle: .yearly) + private let proII_monthly = AccountPlanEntity(type: .proII, name: "Pro II", subscriptionCycle: .monthly) + private let proII_yearly = AccountPlanEntity(type: .proII, name: "Pro II", subscriptionCycle: .yearly) + private let proIII_monthly = AccountPlanEntity(type: .proII, name: "Pro III", subscriptionCycle: .monthly) + private let proIII_yearly = AccountPlanEntity(type: .proII, name: "Pro III", subscriptionCycle: .yearly) func testCreateAccountPlanViewModel_withSelectedPlanType_shouldReturnViewModel() { let details = AccountDetailsEntity(proLevel: .free) diff --git a/MEGAUnitTests/SMSVerification/AddPhoneNumberViewModelTests.swift b/MEGAUnitTests/SMSVerification/AddPhoneNumberViewModelTests.swift index c4a70363aa..dbac0085f3 100644 --- a/MEGAUnitTests/SMSVerification/AddPhoneNumberViewModelTests.swift +++ b/MEGAUnitTests/SMSVerification/AddPhoneNumberViewModelTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import MEGAPresentation import MEGASwift import XCTest diff --git a/MEGAUnitTests/SMSVerification/SMSVerificationViewModelTests.swift b/MEGAUnitTests/SMSVerification/SMSVerificationViewModelTests.swift index 4c9556a3a5..9c79a4597c 100644 --- a/MEGAUnitTests/SMSVerification/SMSVerificationViewModelTests.swift +++ b/MEGAUnitTests/SMSVerification/SMSVerificationViewModelTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class SMSVerificationViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/SMSVerification/VerificationCodeViewModelTests.swift b/MEGAUnitTests/SMSVerification/VerificationCodeViewModelTests.swift index b3db8cd704..75eb47c772 100644 --- a/MEGAUnitTests/SMSVerification/VerificationCodeViewModelTests.swift +++ b/MEGAUnitTests/SMSVerification/VerificationCodeViewModelTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class VerificationCodeViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/Settings/CookieSettingsViewModelTests.swift b/MEGAUnitTests/Settings/CookieSettingsViewModelTests.swift index 0aab51f747..46e08f82a6 100644 --- a/MEGAUnitTests/Settings/CookieSettingsViewModelTests.swift +++ b/MEGAUnitTests/Settings/CookieSettingsViewModelTests.swift @@ -1,7 +1,7 @@ - @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest class CookieSettingsViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/Settings/FeatureFlagViewModelTests.swift b/MEGAUnitTests/Settings/FeatureFlagViewModelTests.swift index 548d1a8ecb..a067617812 100644 --- a/MEGAUnitTests/Settings/FeatureFlagViewModelTests.swift +++ b/MEGAUnitTests/Settings/FeatureFlagViewModelTests.swift @@ -1,4 +1,3 @@ - @testable import MEGA import MEGADomain import MEGADomainMock diff --git a/MEGAUnitTests/Slideshow/SlideShowOptionViewModelTests.swift b/MEGAUnitTests/Slideshow/SlideShowOptionViewModelTests.swift index 9aa4af00b8..2ce26f91c5 100644 --- a/MEGAUnitTests/Slideshow/SlideShowOptionViewModelTests.swift +++ b/MEGAUnitTests/Slideshow/SlideShowOptionViewModelTests.swift @@ -1,5 +1,6 @@ @testable import MEGA import MEGADomain +import MEGAL10n import XCTest final class SlideShowOptionViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/TextEditor/TextEditorViewModelTests.swift b/MEGAUnitTests/TextEditor/TextEditorViewModelTests.swift index b8dfb10403..76ae91a6c6 100644 --- a/MEGAUnitTests/TextEditor/TextEditorViewModelTests.swift +++ b/MEGAUnitTests/TextEditor/TextEditorViewModelTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class TextEditorViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/Transfers/CancellableTransferViewModelTests.swift b/MEGAUnitTests/Transfers/CancellableTransferViewModelTests.swift index 15a1db8e80..4db2402347 100644 --- a/MEGAUnitTests/Transfers/CancellableTransferViewModelTests.swift +++ b/MEGAUnitTests/Transfers/CancellableTransferViewModelTests.swift @@ -1,4 +1,3 @@ - @testable import MEGA import MEGADomain import MEGADomainMock diff --git a/MEGAUnitTests/Utils/DomainExtensions/AlbumNameUseCase+AdditionsTests.swift b/MEGAUnitTests/Utils/DomainExtensions/AlbumNameUseCase+AdditionsTests.swift index afab2bad3d..d8de56443a 100644 --- a/MEGAUnitTests/Utils/DomainExtensions/AlbumNameUseCase+AdditionsTests.swift +++ b/MEGAUnitTests/Utils/DomainExtensions/AlbumNameUseCase+AdditionsTests.swift @@ -1,6 +1,7 @@ @testable import MEGA import MEGADomain import MEGADomainMock +import MEGAL10n import XCTest final class AlbumNameUseCase_AdditionsTests: XCTestCase { diff --git a/MEGAUnitTests/Utils/ViewControllers/TurnOnNotifications/TurnOnNotificationsViewModelTests.swift b/MEGAUnitTests/Utils/ViewControllers/TurnOnNotifications/TurnOnNotificationsViewModelTests.swift index aa85513b74..31423db8c4 100644 --- a/MEGAUnitTests/Utils/ViewControllers/TurnOnNotifications/TurnOnNotificationsViewModelTests.swift +++ b/MEGAUnitTests/Utils/ViewControllers/TurnOnNotifications/TurnOnNotificationsViewModelTests.swift @@ -1,5 +1,6 @@ @testable import MEGA import MEGADomainMock +import MEGAL10n import XCTest final class TurnOnNotificationsViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/Warning/WarningViewModelTests.swift b/MEGAUnitTests/Warning/WarningViewModelTests.swift index e4fe99327f..c65f891c97 100644 --- a/MEGAUnitTests/Warning/WarningViewModelTests.swift +++ b/MEGAUnitTests/Warning/WarningViewModelTests.swift @@ -1,4 +1,5 @@ @testable import MEGA +import MEGAL10n import XCTest final class WarningViewModelTests: XCTestCase { diff --git a/MEGAUnitTests/Widget/QuickAccessWidgetManagerTests.swift b/MEGAUnitTests/Widget/QuickAccessWidgetManagerTests.swift new file mode 100644 index 0000000000..8bf6efb692 --- /dev/null +++ b/MEGAUnitTests/Widget/QuickAccessWidgetManagerTests.swift @@ -0,0 +1,48 @@ +@testable import MEGA +import MEGADomain +import MEGASDKRepoMock +import XCTest + +final class QuickAccessWidgetManagerTests: XCTestCase { + func testQuickAccessWidgetManager_updateFavoritesCalledWithFavouriteChange_shouldCallFavouriteItemsUseCaseInsertFavouriteItem() { + let sut = QuickAccessWidgetManager(favouriteItemsUseCase: MockFavouriteItemsUseCase()) + + let mockNodes: [MockNode] = [ + .init(handle: 1, name: "first", changeType: .favourite, isFavourite: true), + .init(handle: 2, name: "second", changeType: .favourite, isFavourite: false) + ] + + let mockNodeList = MockNodeList(nodes: mockNodes) + + sut.updateFavouritesWidget(for: mockNodeList) + } +} + +private extension QuickAccessWidgetManagerTests { + struct MockFavouriteItemsUseCase: FavouriteItemsUseCaseProtocol { + func insertFavouriteItem(_ item: MEGADomain.FavouriteItemEntity) { + XCTAssertEqual(item.base64Handle, "1") + XCTAssertEqual(item.name, "first") + } + + func deleteFavouriteItem(with base64Handle: MEGADomain.Base64HandleEntity) { + XCTAssertEqual("2", base64Handle) + } + + func createFavouriteItems(_ items: [MEGADomain.FavouriteItemEntity], completion: @escaping (Result) -> Void) { + // no-op + } + + func batchInsertFavouriteItems(_ items: [MEGADomain.FavouriteItemEntity], completion: @escaping (Result) -> Void) { + // no-op + } + + func fetchAllFavouriteItems() -> [MEGADomain.FavouriteItemEntity] { + [] + } + + func fetchFavouriteItems(upTo count: Int) -> [MEGADomain.FavouriteItemEntity] { + [] + } + } +} diff --git a/Modules/DataSource/MEGAChatSDK/Sources/MEGAChatSDK b/Modules/DataSource/MEGAChatSDK/Sources/MEGAChatSDK index 67521dccdc..8dce56e7b2 160000 --- a/Modules/DataSource/MEGAChatSDK/Sources/MEGAChatSDK +++ b/Modules/DataSource/MEGAChatSDK/Sources/MEGAChatSDK @@ -1 +1 @@ -Subproject commit 67521dccdc1ccdeb27d55e204786a65b4ed43ccf +Subproject commit 8dce56e7b2b75e2a2773c12bf1835a968ee6a337 diff --git a/Modules/DataSource/MEGASDK/Sources/MEGASDK b/Modules/DataSource/MEGASDK/Sources/MEGASDK index e6fa199f17..35c857e359 160000 --- a/Modules/DataSource/MEGASDK/Sources/MEGASDK +++ b/Modules/DataSource/MEGASDK/Sources/MEGASDK @@ -1 +1 @@ -Subproject commit e6fa199f17c802550cc674fdf786479b2d100ac6 +Subproject commit 35c857e359b2a27660f50c3a31336eee4cf5a17f diff --git a/Modules/Domain/MEGAAnalyticsDomain/Package.swift b/Modules/Domain/MEGAAnalyticsDomain/Package.swift index 2db741f742..725bc7c031 100644 --- a/Modules/Domain/MEGAAnalyticsDomain/Package.swift +++ b/Modules/Domain/MEGAAnalyticsDomain/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "MEGAAnalyticsDomain", platforms: [ @@ -23,12 +25,12 @@ let package = Package( .target( name: "MEGAAnalyticsDomain", dependencies: ["MEGADomain"], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + swiftSettings: settings ), .target( name: "MEGAAnalyticsDomainMock", dependencies: ["MEGAAnalyticsDomain"], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + swiftSettings: settings ), .testTarget( name: "MEGAAnalyticsDomainTests", @@ -37,7 +39,7 @@ let package = Package( "MEGAAnalyticsDomainMock", "MEGATest" ], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + swiftSettings: settings ) ] ) diff --git a/Modules/Domain/MEGADomain/Package.swift b/Modules/Domain/MEGADomain/Package.swift index e7ff7a174d..ff86ef5c43 100644 --- a/Modules/Domain/MEGADomain/Package.swift +++ b/Modules/Domain/MEGADomain/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "MEGADomain", platforms: [ @@ -24,14 +26,14 @@ let package = Package( .target( name: "MEGADomain", dependencies: ["MEGASwift", "MEGAFoundation"], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")]), + swiftSettings: settings), .target( name: "MEGADomainMock", dependencies: ["MEGADomain"], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")]), + swiftSettings: settings), .testTarget( name: "MEGADomainTests", dependencies: ["MEGADomain", "MEGADomainMock", "MEGATest"], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")]) + swiftSettings: settings) ] ) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/ABTest/ABTestFlagName.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/ABTest/ABTestFlagName.swift index 606f4078d1..b0b039ad22 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/ABTest/ABTestFlagName.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/ABTest/ABTestFlagName.swift @@ -1,2 +1 @@ - public typealias ABTestFlagName = String diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/AchievementTypeEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/AchievementTypeEntity.swift index df930bc85f..0bfa92e539 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/AchievementTypeEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/AchievementTypeEntity.swift @@ -1,4 +1,3 @@ - public enum AchievementTypeEntity: CaseIterable, Sendable { case welcome case invite diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/CheckSMSErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/CheckSMSErrorEntity.swift index e4bb9df957..b673362671 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/CheckSMSErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/CheckSMSErrorEntity.swift @@ -1,4 +1,3 @@ - public enum CheckSMSErrorEntity: Error { case generic case reachedDailyLimit diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/GetSMSErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/GetSMSErrorEntity.swift index 66545f47d3..d065072a81 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/GetSMSErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/GetSMSErrorEntity.swift @@ -1,4 +1,3 @@ - public enum GetSMSErrorEntity: Error, CaseIterable { case generic case failedToGetCallingCodes diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/RegionEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/RegionEntity.swift index bf5fc53da9..3be369ad11 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/RegionEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/RegionEntity.swift @@ -1,4 +1,3 @@ - public typealias RegionCode = String public struct RegionEntity: Equatable { diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/SMSStateEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/SMSStateEntity.swift index 054d3b1a4c..32e987526c 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/SMSStateEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Account/SMS/SMSStateEntity.swift @@ -1,4 +1,3 @@ - public enum SMSStateEntity: Int, CaseIterable { case notAllowed = 0 case onlyUnblock diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Ads/AdsFlagEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Ads/AdsFlagEntity.swift new file mode 100644 index 0000000000..a947801f7e --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Ads/AdsFlagEntity.swift @@ -0,0 +1,9 @@ +public enum AdsFlagEntity: Sendable { + case defaultAds + case forceAds + case ignoreMega + case ignoreCountry + case ignoreIP + case ignorePro + case ignoreRollout +} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Ads/AdsSlotEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Ads/AdsSlotEntity.swift new file mode 100644 index 0000000000..ef6efb7cbc --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Ads/AdsSlotEntity.swift @@ -0,0 +1,6 @@ +public enum AdsSlotEntity: String, Sendable { + case files = "IOSFB" // Files Bottom + case home = "IOSHB" // Home Bottom + case photos = "IOSPHB" // Photos Bottom + case sharedLink = "IOSLINKB" // Shared Link Bottom +} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Album/AlbumEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Album/AlbumEntity.swift index 53c0e3aad7..a19ec7df6c 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Album/AlbumEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Album/AlbumEntity.swift @@ -9,16 +9,24 @@ public enum AlbumEntityType: Sendable { public struct AlbumEntity: Identifiable, Hashable, Sendable { public let id: HandleEntity - public let name: String + public var name: String public var coverNode: NodeEntity? public var count: Int public let type: AlbumEntityType public let creationTime: Date? public let modificationTime: Date? public var sharedLinkStatus: SharedLinkStatusEntity + public let metaData: AlbumMetaDataEntity? - public init(id: HandleEntity, name: String, coverNode: NodeEntity?, count: Int, type: AlbumEntityType, creationTime: Date? = nil, modificationTime: Date? = nil, - sharedLinkStatus: SharedLinkStatusEntity = .unavailable) { + public init(id: HandleEntity, + name: String, + coverNode: NodeEntity?, + count: Int, + type: AlbumEntityType, + creationTime: Date? = nil, + modificationTime: Date? = nil, + sharedLinkStatus: SharedLinkStatusEntity = .unavailable, + metaData: AlbumMetaDataEntity? = nil) { self.id = id self.name = name self.coverNode = coverNode @@ -27,15 +35,11 @@ public struct AlbumEntity: Identifiable, Hashable, Sendable { self.creationTime = creationTime self.modificationTime = modificationTime self.sharedLinkStatus = sharedLinkStatus + self.metaData = metaData } } extension AlbumEntity { - public func update(name newName: String) -> AlbumEntity { - AlbumEntity(id: self.id, name: newName, coverNode: self.coverNode, count: self.count, type: self.type, creationTime: creationTime, - modificationTime: self.modificationTime, sharedLinkStatus: self.sharedLinkStatus) - } - public var systemAlbum: Bool { type == .raw || type == .gif || type == .favourite } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Album/AlbumMetaDataEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Album/AlbumMetaDataEntity.swift new file mode 100644 index 0000000000..f07a3e8666 --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Album/AlbumMetaDataEntity.swift @@ -0,0 +1,11 @@ +import Foundation + +public struct AlbumMetaDataEntity: Hashable, Sendable { + public let imageCount: Int + public let videoCount: Int + + public init(imageCount: Int, videoCount: Int) { + self.imageCount = imageCount + self.videoCount = videoCount + } +} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/AccountPlanAnalyticsEventEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/AccountPlanAnalyticsEventEntity.swift index fdb665d96d..fe18f9cdfc 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/AccountPlanAnalyticsEventEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/AccountPlanAnalyticsEventEntity.swift @@ -1,4 +1,3 @@ - public enum AccountPlanAnalyticsEventEntity { case tapAccountPlanFreePlan case tapAccountPlanProLite diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/AnalyticsEventEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/AnalyticsEventEntity.swift index af985bda00..1822535bd9 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/AnalyticsEventEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/AnalyticsEventEntity.swift @@ -1,4 +1,3 @@ - public enum AnalyticsEventEntity: Equatable { case mediaDiscovery(MediaDiscoveryAnalyticsEventEntity) case meetings(MeetingsAnalyticsEventEntity) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/DownloadAnalyticsEventEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/DownloadAnalyticsEventEntity.swift index eada416148..29a2046948 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/DownloadAnalyticsEventEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/DownloadAnalyticsEventEntity.swift @@ -1,4 +1,3 @@ - public enum DownloadAnalyticsEventEntity { case makeAvailableOfflinePhotosVideos case saveToPhotos diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/ExtensionsAnalyticsEventEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/ExtensionsAnalyticsEventEntity.swift index f1aae8d634..eba916ce4f 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/ExtensionsAnalyticsEventEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/ExtensionsAnalyticsEventEntity.swift @@ -1,4 +1,3 @@ - public enum ExtensionsAnalyticsEventEntity { case withoutNoDDatabase } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/MediaDiscoveryAnalyticsEventEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/MediaDiscoveryAnalyticsEventEntity.swift index c6e3d39c6a..dee2d72d92 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/MediaDiscoveryAnalyticsEventEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/MediaDiscoveryAnalyticsEventEntity.swift @@ -1,4 +1,3 @@ - public enum MediaDiscoveryAnalyticsEventEntity { case clickMediaDiscovery case stayOnMediaDiscoveryOver10s diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/MeetingsAnalyticsEventEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/MeetingsAnalyticsEventEntity.swift index b5c37837f6..ddf7894500 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/MeetingsAnalyticsEventEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/MeetingsAnalyticsEventEntity.swift @@ -1,4 +1,3 @@ - public enum MeetingsAnalyticsEventEntity { case endCallForAll case endCallInNoParticipantsPopup diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/NSEAnalyticsEventEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/NSEAnalyticsEventEntity.swift index 13382fb13e..bc71fb6198 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/NSEAnalyticsEventEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/AnalyticsEvents/NSEAnalyticsEventEntity.swift @@ -1,4 +1,3 @@ - public enum NSEAnalyticsEventEntity { case delayBetweenChatdAndApi case delayBetweenApiAndPushserver diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Banner/BannerEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Banner/BannerEntity.swift new file mode 100644 index 0000000000..9b811d8efc --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Banner/BannerEntity.swift @@ -0,0 +1,24 @@ +import Foundation + +public struct BannerEntity { + public let identifier: Int + public let title: String + public let description: String + public let backgroundImageURL: URL + public let imageURL: URL + public let url: URL? + + public init(identifier: Int, + title: String, + description: String, + backgroundImageURL: URL, + imageURL: URL, + url: URL? = nil) { + self.identifier = identifier + self.title = title + self.description = description + self.backgroundImageURL = backgroundImageURL + self.imageURL = imageURL + self.url = url + } +} diff --git a/iMEGA/Home/UseCase/Domain/Error/BannerErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Banner/BannerErrorEntity.swift similarity index 64% rename from iMEGA/Home/UseCase/Domain/Error/BannerErrorEntity.swift rename to Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Banner/BannerErrorEntity.swift index 7498297b2d..6bbf3ef93d 100644 --- a/iMEGA/Home/UseCase/Domain/Error/BannerErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Banner/BannerErrorEntity.swift @@ -1,12 +1,6 @@ -import Foundation - -enum BannerErrorEntity: Error { - +public enum BannerErrorEntity: Error { case unexpected - case userSessionTimeout - case `internal` - case resourceDoesNotExist } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/CallEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/CallEntity.swift index 0a47a75931..aac9a31303 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/CallEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/CallEntity.swift @@ -39,6 +39,14 @@ public struct CallEntity: Sendable { case outgoingRingingStop case ownPermission case genericNotification + case waitingRoomAllow + case waitingRoomDeny + case waitingRoomComposition + case waitingRoomUsersEntered + case waitingRoomUsersLeave + case waitingRoomUsersAllow + case waitingRoomUsersDeny + case waitingRoomPushedFromCall } public enum ConfigurationType: Sendable { @@ -56,12 +64,13 @@ public struct CallEntity: Sendable { public enum NotificationType: Sendable { case invalid case serverError + case sfuDeny } public let status: CallStatusType? public let chatId: HandleEntity public let callId: HandleEntity - public let changeTye: ChangeType? + public let changeType: ChangeType? public let duration: Int64 public let initialTimestamp: Int64 public let finalTimestamp: Int64 @@ -75,13 +84,16 @@ public struct CallEntity: Sendable { public let sessionClientIds: [HandleEntity] public let clientSessions: [ChatSessionEntity] public let participants: [HandleEntity] + public let waitingRoomStatus: WaitingRoomStatus + public let waitingRoom: WaitingRoomEntity? + public let waitingRoomHandleList: [HandleEntity] public let uuid: UUID - public init(status: CallStatusType?, chatId: HandleEntity, callId: HandleEntity, changeTye: ChangeType?, duration: Int64, initialTimestamp: Int64, finalTimestamp: Int64, hasLocalAudio: Bool, hasLocalVideo: Bool, termCodeType: TermCodeType?, isRinging: Bool, callCompositionChange: CompositionChangeType?, numberOfParticipants: Int, isOnHold: Bool, sessionClientIds: [HandleEntity], clientSessions: [ChatSessionEntity], participants: [HandleEntity], uuid: UUID) { + public init(status: CallStatusType?, chatId: HandleEntity, callId: HandleEntity, changeType: ChangeType?, duration: Int64, initialTimestamp: Int64, finalTimestamp: Int64, hasLocalAudio: Bool, hasLocalVideo: Bool, termCodeType: TermCodeType?, isRinging: Bool, callCompositionChange: CompositionChangeType?, numberOfParticipants: Int, isOnHold: Bool, sessionClientIds: [HandleEntity], clientSessions: [ChatSessionEntity], participants: [HandleEntity], waitingRoomStatus: WaitingRoomStatus, waitingRoom: WaitingRoomEntity?, waitingRoomHandleList: [HandleEntity], uuid: UUID) { self.status = status self.chatId = chatId self.callId = callId - self.changeTye = changeTye + self.changeType = changeType self.duration = duration self.initialTimestamp = initialTimestamp self.finalTimestamp = finalTimestamp @@ -95,6 +107,9 @@ public struct CallEntity: Sendable { self.sessionClientIds = sessionClientIds self.clientSessions = clientSessions self.participants = participants + self.waitingRoomStatus = waitingRoomStatus + self.waitingRoom = waitingRoom + self.waitingRoomHandleList = waitingRoomHandleList self.uuid = uuid } } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ChatRoomPrivilegeEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ChatRoomPrivilegeEntity.swift index 7b76fca4e4..761c4d6977 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ChatRoomPrivilegeEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ChatRoomPrivilegeEntity.swift @@ -1,4 +1,3 @@ - public enum ChatRoomPrivilegeEntity: Sendable { case unknown case removed diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ChatSessionEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ChatSessionEntity.swift index 505f6aed39..cb88e91377 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ChatSessionEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ChatSessionEntity.swift @@ -1,4 +1,3 @@ - public struct ChatSessionEntity: Sendable { public enum StatusType: Sendable { case invalid diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/NetworkQuality.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/NetworkQuality.swift index e7bc40faa8..54f8a4f536 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/NetworkQuality.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/NetworkQuality.swift @@ -1,4 +1,3 @@ - public enum NetworkQuality: Int { case bad = 0 case good diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ScheduledMeetingErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ScheduledMeetingErrorEntity.swift index cb07b0bf78..3393ba730c 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ScheduledMeetingErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/ScheduledMeetingErrorEntity.swift @@ -1,4 +1,3 @@ - public enum ScheduledMeetingErrorEntity: Error { case meetingNotFound } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/StartCallEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/StartCallEntity.swift index 8a9b9c8616..4b7b0bc3e3 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/StartCallEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/StartCallEntity.swift @@ -1,4 +1,3 @@ - public struct StartCallEntity { public let meetingName: String public let enableVideo: Bool diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/WaitingRoomEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/WaitingRoomEntity.swift new file mode 100644 index 0000000000..24f7cea706 --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Call/WaitingRoomEntity.swift @@ -0,0 +1,13 @@ +public struct WaitingRoomEntity: Sendable { + public let sessionClientIds: [HandleEntity] + + public init(sessionClientIds: [HandleEntity]) { + self.sessionClientIds = sessionClientIds + } +} + +public enum WaitingRoomStatus: Sendable { + case unknown + case notAllowed + case allowed +} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatConnectionStatus.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatConnectionStatus.swift index 471f6f6a0f..ff72be151d 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatConnectionStatus.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatConnectionStatus.swift @@ -1,4 +1,3 @@ - public enum ChatConnectionStatus { case invalid case offline diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatContainsMetaEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatContainsMetaEntity.swift index e2ecf03516..d333cc6bc3 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatContainsMetaEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatContainsMetaEntity.swift @@ -1,4 +1,3 @@ - public struct ChatContainsMetaEntity { public enum MetaType { case invalid diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatGeolocationEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatGeolocationEntity.swift index 5b54e5cb78..74f9fba767 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatGeolocationEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatGeolocationEntity.swift @@ -1,4 +1,3 @@ - public struct ChatGeolocationEntity { public let longitude: Float public let latitude: Float diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatGiphyEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatGiphyEntity.swift index 06e4f94c68..1d0b952eac 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatGiphyEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatGiphyEntity.swift @@ -1,4 +1,3 @@ - public struct ChatGiphyEntity { public let mp4Src: String? public let webpSrc: String? diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatIdEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatIdEntity.swift index 70e5f46454..f0ad66a8e9 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatIdEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatIdEntity.swift @@ -1,2 +1 @@ - public typealias ChatIdEntity = UInt64 diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageEndCallReasonEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageEndCallReasonEntity.swift index a51f7d68f5..55d54ecbd4 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageEndCallReasonEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageEndCallReasonEntity.swift @@ -1,4 +1,3 @@ - public enum ChatMessageEndCallReasonEntity { case ended case rejected diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageScheduledMeetingChangeType.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageScheduledMeetingChangeType.swift index 7c80babd1e..ae334693be 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageScheduledMeetingChangeType.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageScheduledMeetingChangeType.swift @@ -1,4 +1,3 @@ - public enum ChatMessageScheduledMeetingChangeType { case none case parent diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageStatusEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageStatusEntity.swift index d0c355a1a3..cdfe09c904 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageStatusEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatMessageStatusEntity.swift @@ -1,4 +1,3 @@ - public enum ChatMessageStatusEntity { case unknown case sending diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRichPreviewEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRichPreviewEntity.swift index 60fd181720..f92957dd3a 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRichPreviewEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRichPreviewEntity.swift @@ -1,4 +1,3 @@ - public struct ChatRichPreviewEntity { public let text: String? public let title: String? diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRoomDelegateEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRoomDelegateEntity.swift index 2a4e05c385..4a95f13867 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRoomDelegateEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRoomDelegateEntity.swift @@ -1,4 +1,3 @@ - public struct ChatRoomDelegateEntity { public var onChatRoomUpdate: ((ChatRoomEntity) -> Void)? public var onMessageLoaded: ((ChatMessageEntity?) -> Void)? diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRoomErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRoomErrorEntity.swift index 008a32a052..5a638ea4d0 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRoomErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatRoomErrorEntity.swift @@ -1,4 +1,3 @@ - public enum ChatRoomErrorEntity: Error { case generic case emptyTextResponse diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatSourceEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatSourceEntity.swift index 3ed7d01c92..87a1ad4675 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatSourceEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatSourceEntity.swift @@ -1,4 +1,3 @@ - public enum ChatSourceEntity { case error case none diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatStatusEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatStatusEntity.swift index c41d00712d..faaa4cdd8d 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatStatusEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatStatusEntity.swift @@ -1,4 +1,3 @@ - public enum ChatStatusEntity: CaseIterable { case offline case away diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatTypeEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatTypeEntity.swift index d9941aaef6..1cc695d8d0 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatTypeEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ChatTypeEntity.swift @@ -1,4 +1,3 @@ - public enum ChatTypeEntity { case all case individual diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ManageChatHistoryErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ManageChatHistoryErrorEntity.swift index 67c32a851d..a51935538b 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ManageChatHistoryErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ManageChatHistoryErrorEntity.swift @@ -1,4 +1,3 @@ - public enum ManageChatHistoryErrorEntity: Error { case generic case chatIdInvalid diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ScheduledMeeting/ScheduleMeetingErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ScheduledMeeting/ScheduleMeetingErrorEntity.swift index d8c4b40e3c..e20473420f 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ScheduledMeeting/ScheduleMeetingErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Chat/ScheduledMeeting/ScheduleMeetingErrorEntity.swift @@ -1,4 +1,3 @@ - public enum ScheduleMeetingErrorEntity: Error { case generic case invalidArguments diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/ContextMenu/ContextMenuActions.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/ContextMenu/ContextMenuActions.swift index 6542c8d3eb..71f0b6b3c5 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/ContextMenu/ContextMenuActions.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/ContextMenu/ContextMenuActions.swift @@ -1,4 +1,3 @@ - // MARK: - Context Menu different types /// Different context menu items types defined in the app diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/DeviceCenter/BackupSubstateEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/DeviceCenter/BackupSubstateEntity.swift index 6840cb4479..6186813765 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/DeviceCenter/BackupSubstateEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/DeviceCenter/BackupSubstateEntity.swift @@ -1,4 +1,3 @@ - public enum BackupSubstateEntity: Sendable { case noSyncError case unknownError diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/NameCollision/NameCollisionActionType.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/NameCollision/NameCollisionActionType.swift index 1a09f8883a..a999dc44f5 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/NameCollision/NameCollisionActionType.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/NameCollision/NameCollisionActionType.swift @@ -1,4 +1,3 @@ - public enum NameCollisionActionType { case update case replace diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/NameCollision/NameCollisionType.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/NameCollision/NameCollisionType.swift index ea2a6015d0..4cbb3638da 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/NameCollision/NameCollisionType.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/NameCollision/NameCollisionType.swift @@ -1,4 +1,3 @@ - public enum NameCollisionType { case upload case move diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/CopyOrMoveErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/CopyOrMoveErrorEntity.swift index db2aed1896..4d395e4265 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/CopyOrMoveErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/CopyOrMoveErrorEntity.swift @@ -1,4 +1,3 @@ - public enum CopyOrMoveErrorEntity: Error { case generic case nodeNotFound diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/ExportFileErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/ExportFileErrorEntity.swift index a701b5204c..f9143b08a9 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/ExportFileErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/ExportFileErrorEntity.swift @@ -1,4 +1,3 @@ - public enum ExportFileErrorEntity: Error { case generic case notEnoughSpace diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/NodeFavouriteErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/NodeFavouriteErrorEntity.swift index 6a84331a7a..069a84df1c 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/NodeFavouriteErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/NodeFavouriteErrorEntity.swift @@ -1,4 +1,3 @@ - public enum NodeFavouriteErrorEntity: Error { case generic case nodeNotFound diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/SaveMediaToPhotosErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/SaveMediaToPhotosErrorEntity.swift index 16affc2cfb..82aa6a07b7 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/SaveMediaToPhotosErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Node/SaveMediaToPhotosErrorEntity.swift @@ -1,4 +1,3 @@ - public enum SaveMediaToPhotosErrorEntity: Error { case imageNotSaved case videoNotSaved diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanEntity.swift index 2ee5243c88..f5421c0bfd 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanEntity.swift @@ -1,10 +1,9 @@ - public struct AccountPlanEntity: Sendable { public let productIdentifier: String public var type: AccountTypeEntity public var name: String public var currency: String - public var term: AccountPlanTermEntity + public var subscriptionCycle: SubscriptionCycleEntity public var storage: String public var transfer: String public var price: Double @@ -14,7 +13,7 @@ public struct AccountPlanEntity: Sendable { type: AccountTypeEntity = .free, name: String = "", currency: String = "", - term: AccountPlanTermEntity = .none, + subscriptionCycle: SubscriptionCycleEntity = .none, storage: String = "", transfer: String = "", price: Double = 0, @@ -23,7 +22,7 @@ public struct AccountPlanEntity: Sendable { self.type = type self.name = name self.currency = currency - self.term = term + self.subscriptionCycle = subscriptionCycle self.storage = storage self.transfer = transfer self.price = price @@ -33,13 +32,13 @@ public struct AccountPlanEntity: Sendable { extension AccountPlanEntity: Equatable { public static func == (lhs: AccountPlanEntity, rhs: AccountPlanEntity) -> Bool { - lhs.type == rhs.type && lhs.term == rhs.term + lhs.type == rhs.type && lhs.subscriptionCycle == rhs.subscriptionCycle } } extension AccountPlanEntity: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(type) - hasher.combine(term) + hasher.combine(subscriptionCycle) } } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanErrorEntity.swift index f974375a70..42615f637b 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanErrorEntity.swift @@ -1,4 +1,3 @@ - public enum AccountPlanPurchaseErrorEntity { case paymentCancelled, paymentInvalid, paymentNotAllowed, unknown } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanTagEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanTagEntity.swift index d47622dbbc..bcd76bd08a 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanTagEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanTagEntity.swift @@ -1,4 +1,3 @@ - public enum AccountPlanTagEntity { case currentPlan, recommended, none } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanTermEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanTermEntity.swift deleted file mode 100644 index e72a594c74..0000000000 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Plan/AccountPlanTermEntity.swift +++ /dev/null @@ -1,4 +0,0 @@ - -public enum AccountPlanTermEntity: Sendable { - case monthly, yearly, none -} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Requests/AccountRequestEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Requests/AccountRequestEntity.swift index 2ad6b2344b..4656c22b17 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Requests/AccountRequestEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Requests/AccountRequestEntity.swift @@ -1,4 +1,3 @@ - public struct AccountRequestEntity { public var type: RequestTypeEntity public var file: String? diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/APIEnvironmentEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/APIEnvironmentEntity.swift index 7c154198d8..e9709a4d7f 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/APIEnvironmentEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/APIEnvironmentEntity.swift @@ -1,4 +1,3 @@ - public enum APIEnvironmentEntity { case production case staging diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/AppConfigurationEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/AppConfigurationEntity.swift index 93c0591a8c..78212df28f 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/AppConfigurationEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/AppConfigurationEntity.swift @@ -1,4 +1,3 @@ - public enum AppConfigurationEntity: CaseIterable { case debug case qa diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/CookieSettingsErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/CookieSettingsErrorEntity.swift index 458ae88f91..66b38fd4fc 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/CookieSettingsErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/CookieSettingsErrorEntity.swift @@ -1,4 +1,3 @@ - import Foundation public enum CookieSettingsErrorEntity: Error { diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/Help/FeedbackEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/Help/FeedbackEntity.swift index 285a908982..8642dedcdb 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/Help/FeedbackEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Settings/Help/FeedbackEntity.swift @@ -1,4 +1,3 @@ - public struct FeedbackEntity: Sendable { public let toEmail: String public let subject: String diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Share/ShareErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Share/ShareErrorEntity.swift index 67f177140b..f669ea01d8 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Share/ShareErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Share/ShareErrorEntity.swift @@ -1,4 +1,3 @@ - public enum ShareErrorEntity: Error { case generic case nodeNotFound diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Transfer/FolderTransferUpdateEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Transfer/FolderTransferUpdateEntity.swift index d0f0365136..2a563ddd65 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Transfer/FolderTransferUpdateEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Transfer/FolderTransferUpdateEntity.swift @@ -1,4 +1,3 @@ - public struct FolderTransferUpdateEntity { public let transfer: TransferEntity public let stage: TransferStageEntity diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/ContactLinkEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/ContactLinkEntity.swift index a06fb737fa..2d596e6920 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/ContactLinkEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/ContactLinkEntity.swift @@ -1,4 +1,3 @@ - public struct ContactLinkEntity: Sendable { public let email: String? public let name: String? diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/ContactRequestStatusEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/ContactRequestStatusEntity.swift index d6d88a1105..3e2d3f6b8e 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/ContactRequestStatusEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/ContactRequestStatusEntity.swift @@ -1,4 +1,3 @@ - public enum ContactRequestStatusEntity { case unresolved case accepted diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/InviteErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/InviteErrorEntity.swift index 5cc0dfd007..de7bd61400 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/InviteErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/InviteErrorEntity.swift @@ -1,4 +1,3 @@ - public enum InviteErrorEntity: Error { case generic(String) case ownEmailEntered diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/UserAttributeEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/UserAttributeEntity.swift index 6c458ac6b9..ebacf07414 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/UserAttributeEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/UserAttributeEntity.swift @@ -1,4 +1,3 @@ - public enum UserAttributeEntity: Sendable { case avatar case firstName diff --git a/MEGADomain/Entity/Error/UserImageLoadErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/UserImageLoadErrorEntity.swift similarity index 71% rename from MEGADomain/Entity/Error/UserImageLoadErrorEntity.swift rename to Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/UserImageLoadErrorEntity.swift index 69908b753c..9f0a1cef34 100644 --- a/MEGADomain/Entity/Error/UserImageLoadErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/User/UserImageLoadErrorEntity.swift @@ -1,5 +1,4 @@ - -enum UserImageLoadErrorEntity: Error { +public enum UserImageLoadErrorEntity: Error { case generic case base64EncodingError case unableToFetch diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Versions/FileVersionErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Versions/FileVersionErrorEntity.swift index 00313d3e95..c531d7800b 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Versions/FileVersionErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Versions/FileVersionErrorEntity.swift @@ -1,4 +1,3 @@ - public enum FileVersionErrorEntity: Error, CaseIterable { case generic case optionNeverSet diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Video/CodecIdEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Video/CodecIdEntity.swift index 33295a9c21..4de6b0c035 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Video/CodecIdEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Video/CodecIdEntity.swift @@ -1,2 +1 @@ - public typealias CodecIdEntity = Int diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Video/ShortFormatEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Video/ShortFormatEntity.swift index 6e86ad0f9c..cee2d86ae8 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Video/ShortFormatEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Video/ShortFormatEntity.swift @@ -1,2 +1 @@ - public typealias ShortFormatEntity = Int diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/ABTest/ABTestinRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/ABTest/ABTestinRepositoryProtocol.swift index c0af7180a2..d0834681c5 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/ABTest/ABTestinRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/ABTest/ABTestinRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol ABTestRepositoryProtocol: RepositoryProtocol { func abTestValue(for: ABTestFlagName) async -> Int } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Account/PSARepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Account/PSARepositoryProtocol.swift index 87136826cf..bd4617b02f 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Account/PSARepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Account/PSARepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol PSARepositoryProtocol: RepositoryProtocol { func getPSA(completion: @escaping (Result) -> Void) func markAsSeenForPSA(withIdentifier identifier: PSAIdentifier) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Account/SMS/SMSRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Account/SMS/SMSRepositoryProtocol.swift index f2f9e6e6ef..b4f2b95e31 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Account/SMS/SMSRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Account/SMS/SMSRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol SMSRepositoryProtocol: RepositoryProtocol { func verifiedPhoneNumber() -> String? func getRegionCallingCodes(completion: @escaping (Result<[RegionEntity], GetSMSErrorEntity>) -> Void) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Ads/AdsRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Ads/AdsRepositoryProtocol.swift new file mode 100644 index 0000000000..47b094e0a8 --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Ads/AdsRepositoryProtocol.swift @@ -0,0 +1,6 @@ +public protocol AdsRepositoryProtocol: RepositoryProtocol { + func fetchAds(adsFlag: AdsFlagEntity, + adUnits: [AdsSlotEntity], + publicHandle: HandleEntity) async throws -> [String: String] + func queryAds(adsFlag: AdsFlagEntity, publicHandle: HandleEntity) async throws -> Int +} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/AudioVideo/AudioSessionRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/AudioVideo/AudioSessionRepositoryProtocol.swift index 378f78818c..de32b973f2 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/AudioVideo/AudioSessionRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/AudioVideo/AudioSessionRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol AudioSessionRepositoryProtocol { var isBluetoothAudioRouteAvailable: Bool { get } var currentSelectedAudioPort: AudioPort { get } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/AudioVideo/CaptureDeviceRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/AudioVideo/CaptureDeviceRepositoryProtocol.swift index 7299094b0c..1638aee8fb 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/AudioVideo/CaptureDeviceRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/AudioVideo/CaptureDeviceRepositoryProtocol.swift @@ -1,3 +1,3 @@ public protocol CaptureDeviceRepositoryProtocol { - func wideAngleCameraLocalizedName(postion: CameraPositionEntity) -> String? + func wideAngleCameraLocalizedName(position: CameraPositionEntity) -> String? } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Banner/BannerRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Banner/BannerRepositoryProtocol.swift new file mode 100644 index 0000000000..1696906e09 --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Banner/BannerRepositoryProtocol.swift @@ -0,0 +1,10 @@ +public protocol BannerRepositoryProtocol: RepositoryProtocol { + func banners( + completion: @escaping (Result<[BannerEntity], BannerErrorEntity>) -> Void + ) + + func dismissBanner( + withBannerId bannerId: Int, + completion: ((Result) -> Void)? + ) +} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Call/CallRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Call/CallRepositoryProtocol.swift index 618b7fede7..08be7e2a9c 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Call/CallRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Call/CallRepositoryProtocol.swift @@ -9,11 +9,16 @@ public protocol CallRepositoryProtocol { func startCall(for chatId: HandleEntity, enableVideo: Bool, enableAudio: Bool) async throws -> CallEntity func startCallNoRinging(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) func startCallNoRinging(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool) async throws -> CallEntity + func startMeetingInWaitingRoomChat(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) + func startMeetingInWaitingRoomChat(for scheduledMeeting: ScheduledMeetingEntity, enableVideo: Bool, enableAudio: Bool) async throws -> CallEntity func joinCall(for chatId: HandleEntity, enableVideo: Bool, enableAudio: Bool, completion: @escaping (Result) -> Void) func hangCall(for callId: HandleEntity) func endCall(for callId: HandleEntity) func addPeer(toCall call: CallEntity, peerId: UInt64) func removePeer(fromCall call: CallEntity, peerId: UInt64) + func allowUsersJoinCall(_ call: CallEntity, users: [UInt64]) + func kickUsersFromCall(_ call: CallEntity, users: [UInt64]) + func pushUsersIntoWaitingRoom(for scheduledMeeting: ScheduledMeetingEntity, users: [UInt64]) func makePeerAModerator(inCall call: CallEntity, peerId: UInt64) func removePeerAsModerator(inCall call: CallEntity, peerId: UInt64) func createActiveSessions() @@ -38,4 +43,6 @@ public protocol CallCallbacksRepositoryProtocol { func chatTitleChanged(chatRoom: ChatRoomEntity) func networkQualityChanged(_ quality: NetworkQuality) func outgoingRingingStopReceived() + func waitingRoomUsersEntered(with handles: [HandleEntity]) + func waitingRoomUsersLeave(with handles: [HandleEntity]) } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/ChatRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/ChatRepositoryProtocol.swift index dd979f8299..6495e95cd3 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/ChatRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/ChatRepositoryProtocol.swift @@ -22,4 +22,5 @@ public protocol ChatRepositoryProtocol: RepositoryProtocol { func monitorChatConnectionStatusUpdate(forChatId chatId: HandleEntity) -> AnyPublisher func monitorChatPrivateModeUpdate(forChatId chatId: HandleEntity) -> AnyPublisher func chatCall(for chatId: HandleEntity) -> CallEntity? + func isCallActive(for chatId: HandleEntity) -> Bool } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/ManageHistoryRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/ManageHistoryRepositoryProtocol.swift index 345d9fe737..1a8012c96d 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/ManageHistoryRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/ManageHistoryRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol ManageChatHistoryRepositoryProtocol { func chatRetentionTime(for chatId: ChatIdEntity, completion: @escaping (Result) -> Void) func setChatRetentionTime(for chatId: ChatIdEntity, period: UInt, completion: @escaping (Result) -> Void) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/WaitingRoomRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/WaitingRoomRepositoryProtocol.swift new file mode 100644 index 0000000000..52bf7b5dc6 --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Chat/WaitingRoomRepositoryProtocol.swift @@ -0,0 +1,5 @@ +import Foundation + +public protocol WaitingRoomRepositoryProtocol { + func userName() -> String +} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Contact/ContactsRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Contact/ContactsRepositoryProtocol.swift index 43d58a60e1..e1b337246e 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Contact/ContactsRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Contact/ContactsRepositoryProtocol.swift @@ -1,4 +1,6 @@ +import Contacts -public protocol ContactsRepositoryProtocol { +public protocol ContactsRepositoryProtocol: RepositoryProtocol { var isAuthorizedToAccessPhoneContacts: Bool { get } + func fetchContacts() -> [CNContact] } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/ContextMenu/CreateContextMenuRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/ContextMenu/CreateContextMenuRepositoryProtocol.swift index 62f053636b..cf727c78d0 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/ContextMenu/CreateContextMenuRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/ContextMenu/CreateContextMenuRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol CreateContextMenuRepositoryProtocol: RepositoryProtocol { func createContextMenu(config: CMConfigEntity) -> CMEntity? } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/FeatureFlag/FeatureFlagRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/FeatureFlag/FeatureFlagRepositoryProtocol.swift index 0548fd03b6..0b1dadf1e3 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/FeatureFlag/FeatureFlagRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/FeatureFlag/FeatureFlagRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol FeatureFlagRepositoryProtocol: RepositoryProtocol { func savedFeatureFlags() -> [FeatureFlagEntity] func isFeatureFlagEnabled(for key: FeatureFlagName) -> Bool diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/MEGAsdk/MEGAHandleRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/MEGAsdk/MEGAHandleRepositoryProtocol.swift index c866a35370..4e8757e279 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/MEGAsdk/MEGAHandleRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/MEGAsdk/MEGAHandleRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol MEGAHandleRepositoryProtocol: RepositoryProtocol { func base64Handle(forUserHandle handle: HandleEntity) -> Base64HandleEntity? func handle(forBase64Handle handle: Base64HandleEntity) -> HandleEntity? diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Network/NetworkMonitorRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Network/NetworkMonitorRepositoryProtocol.swift index 4a233fc677..e56c4a5c74 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Network/NetworkMonitorRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Network/NetworkMonitorRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol NetworkMonitorRepositoryProtocol { func networkPathChanged(completion: @escaping (Bool) -> Void) func isConnected() -> Bool diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/ImportNodeRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/ImportNodeRepositoryProtocol.swift index 4d0ab8ddf0..a7c4a86c93 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/ImportNodeRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/ImportNodeRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol ImportNodeRepositoryProtocol: RepositoryProtocol { func importChatNode(_ node: NodeEntity, messageId: HandleEntity, chatId: HandleEntity, completion: @escaping (Result) -> Void) } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeActionRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeActionRepositoryProtocol.swift index b7de79fb43..5c3b68f466 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeActionRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeActionRepositoryProtocol.swift @@ -1,5 +1,5 @@ public protocol NodeActionRepositoryProtocol: RepositoryProtocol { - func fetchnodes() async throws + func fetchNodes() async throws func createFolder(name: String, parent: NodeEntity) async throws -> NodeEntity func rename(node: NodeEntity, name: String) async throws -> NodeEntity func trash(node: NodeEntity) async throws -> NodeEntity diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeFavouriteActionRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeFavouriteActionRepositoryProtocol.swift index 8ab3e2a121..24d280db34 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeFavouriteActionRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeFavouriteActionRepositoryProtocol.swift @@ -1,4 +1,4 @@ -public protocol NodeFavouriteActionRepositoryProtocol { +public protocol NodeFavouriteActionRepositoryProtocol: RepositoryProtocol { func favourite(node: NodeEntity) async throws func unFavourite(node: NodeEntity) async throws } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeUpdateRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeUpdateRepositoryProtocol.swift index bd24f8d0a5..74ea4c72c7 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeUpdateRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/NodeUpdateRepositoryProtocol.swift @@ -1,6 +1,5 @@ -import Foundation - public protocol NodeUpdateRepositoryProtocol: RepositoryProtocol { - func shouldProcessOnNodesUpdate(parentNode: NodeEntity, childNodes: [NodeEntity], + func shouldProcessOnNodesUpdate(parentNode: NodeEntity, + childNodes: [NodeEntity], updatedNodes: [NodeEntity]) -> Bool } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/RubbishBinRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/RubbishBinRepositoryProtocol.swift index 6d5319a31a..cdbeacfdca 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/RubbishBinRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/RubbishBinRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol RubbishBinRepositoryProtocol { func isSyncDebrisNode(_ node: NodeEntity) -> Bool } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/SearchNodeRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/SearchNodeRepositoryProtocol.swift index 998b48b366..796c083d3e 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/SearchNodeRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Node/SearchNodeRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol SearchNodeRepositoryProtocol: RepositoryProtocol { func search(type: SearchNodeTypeEntity, text: String, sortType: SortOrderEntity) async throws -> [NodeEntity] func cancelSearch() diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Offline/OfflineFileFetcherRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Offline/OfflineFileFetcherRepositoryProtocol.swift index 4d982c3f1f..d949c1a233 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Offline/OfflineFileFetcherRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Offline/OfflineFileFetcherRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol OfflineFileFetcherRepositoryProtocol: RepositoryProtocol { func offlineFiles() -> [OfflineFileEntity] func offlineFile(for base64Handle: String) -> OfflineFileEntity? diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/APIEnvironmentRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/APIEnvironmentRepositoryProtocol.swift index d2f62ecd14..8f738fc756 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/APIEnvironmentRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/APIEnvironmentRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol APIEnvironmentRepositoryProtocol: RepositoryProtocol { func changeAPIURL(_ environment: APIEnvironmentEntity) } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/ChangeSfuServerRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/ChangeSfuServerRepositoryProtocol.swift index 5ba4d346cc..773bed86fa 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/ChangeSfuServerRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/ChangeSfuServerRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol ChangeSfuServerRepositoryProtocol: RepositoryProtocol { func changeSfuServer(to serverId: Int) } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/LogSettingRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/LogSettingRepositoryProtocol.swift index 9ba961852f..ca7cb67638 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/LogSettingRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Settings/LogSettingRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol LogSettingRepositoryProtocol: RepositoryProtocol { func toggleLogs(enable: Bool) } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/DownloadFileRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/DownloadFileRepositoryProtocol.swift index 500a1167fa..7084ffc156 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/DownloadFileRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/DownloadFileRepositoryProtocol.swift @@ -1,7 +1,17 @@ import Foundation public protocol DownloadFileRepositoryProtocol: RepositoryProtocol { + + /// Initiates download to save the given node handle to the passed in destination url. + /// - Parameters: + /// - nodeHandle: Node Handle to be downloaded + /// - url: Location for file to be downloaded to. + /// - metaData: MetaData indicating type of download to start. + /// - Returns: TransferEntity model on completion of download, else will throw TransferErrorEntity + func download(nodeHandle: HandleEntity, to url: URL, metaData: TransferMetaDataEntity?) async throws -> TransferEntity + func download(nodeHandle: HandleEntity, to url: URL, metaData: TransferMetaDataEntity?, completion: @escaping (Result) -> Void) + func downloadChat(nodeHandle: HandleEntity, messageId: HandleEntity, chatId: HandleEntity, to url: URL, metaData: TransferMetaDataEntity?, completion: @escaping (Result) -> Void) func downloadTo(_ url: URL, nodeHandle: HandleEntity, appData: String?, progress: ((TransferEntity) -> Void)?, completion: @escaping (Result) -> Void) func downloadFile(forNodeHandle handle: HandleEntity, to url: URL, filename: String?, appdata: String?, startFirst: Bool, start: ((TransferEntity) -> Void)?, update: ((TransferEntity) -> Void)?, folderUpdate: ((FolderTransferUpdateEntity) -> Void)?, completion: ((Result) -> Void)?) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/TransferInventoryRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/TransferInventoryRepositoryProtocol.swift index 17ebcbeb0f..3658f63bca 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/TransferInventoryRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/TransferInventoryRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol TransferInventoryRepositoryProtocol { func transfers() -> [TransferEntity] func downloadTransfers() -> [TransferEntity] diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/UploadPhotoAssetsRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/UploadPhotoAssetsRepositoryProtocol.swift index 4cf86266c9..a82411624c 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/UploadPhotoAssetsRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Transfer/UploadPhotoAssetsRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol UploadPhotoAssetsRepositoryProtocol { func upload(assets: [String], toParent parentHandle: HandleEntity) } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserAttributeRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserAttributeRepositoryProtocol.swift index 7216362458..1d145a9c63 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserAttributeRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserAttributeRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol UserAttributeRepositoryProtocol { func updateUserAttribute(_ attribute: UserAttributeEntity, value: String) async throws func updateUserAttribute(_ attribute: UserAttributeEntity, key: String, value: String) async throws diff --git a/MEGADomain/RepositoryProtocol/User/UserImageRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserImageRepositoryProtocol.swift similarity index 59% rename from MEGADomain/RepositoryProtocol/User/UserImageRepositoryProtocol.swift rename to Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserImageRepositoryProtocol.swift index e0db7aa21c..6d34591f25 100644 --- a/MEGADomain/RepositoryProtocol/User/UserImageRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserImageRepositoryProtocol.swift @@ -1,11 +1,12 @@ import Combine -import MEGADomain -protocol UserImageRepositoryProtocol: RepositoryProtocol { +public typealias ImageFilePathEntity = String + +public protocol UserImageRepositoryProtocol: RepositoryProtocol { func loadUserImage(withUserHandle handle: String?, destinationPath: String, - completion: @escaping (Result) -> Void) - func avatar(forUserHandle handle: String?, destinationPath: String) async throws -> UIImage + completion: @escaping (Result) -> Void) + func avatar(forUserHandle handle: String?, destinationPath: String) async throws -> ImageFilePathEntity func avatarColorHex(forBase64UserHandle handle: Base64HandleEntity) -> String? mutating func requestAvatarChangeNotification(forUserHandles handles: [HandleEntity]) -> AnyPublisher<[HandleEntity], Never> } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserInviteRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserInviteRepositoryProtocol.swift index ff9ced55f3..5f6b806907 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserInviteRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserInviteRepositoryProtocol.swift @@ -1,5 +1,3 @@ - -public protocol UserInviteRepositoryProtocol { - func sendInvite(forEmail email: String, - completion: @escaping (Result) -> Void) +public protocol UserInviteRepositoryProtocol: RepositoryProtocol { + func sendInvite(forEmail email: String) async throws } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserStoreRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserStoreRepositoryProtocol.swift index adcf0e410a..2889c9d14a 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserStoreRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/User/UserStoreRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol UserStoreRepositoryProtocol: RepositoryProtocol, Sendable { func getDisplayName(forUserHandle handle: UInt64) -> String? func displayName(forUserHandle handle: HandleEntity) async -> String? diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Versions/FileVersionsRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Versions/FileVersionsRepositoryProtocol.swift index 978933dfa4..1aa3bd329d 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Versions/FileVersionsRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Versions/FileVersionsRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol FileVersionsRepositoryProtocol: RepositoryProtocol { func isFileVersionsEnabled(completion: @escaping (Result) -> Void) func enableFileVersions(_ enable: Bool, completion: @escaping (Result) -> Void) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Video/VideoMediaRepositoryProtocol.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Video/VideoMediaRepositoryProtocol.swift index 5578af5893..9d2051d467 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Video/VideoMediaRepositoryProtocol.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Video/VideoMediaRepositoryProtocol.swift @@ -1,4 +1,3 @@ - public protocol VideoMediaRepositoryProtocol: RepositoryProtocol { func isSupportedFormat(_ shortFormat: ShortFormatEntity) -> Bool func isSupportedCodec(_ codecId: CodecIdEntity) -> Bool diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/ABTest/ABTestUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/ABTest/ABTestUseCase.swift index 036fbd0669..f2d75a2808 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/ABTest/ABTestUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/ABTest/ABTestUseCase.swift @@ -1,4 +1,3 @@ - public protocol ABTestUseCaseProtocol { func abTestValue(for: ABTestFlagName) async -> Int } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/AccountUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/AccountUseCase.swift index bbf36f6e22..341a0678a1 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/AccountUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/AccountUseCase.swift @@ -1,4 +1,3 @@ - // MARK: - Use case protocol public protocol AccountUseCaseProtocol { var currentUserHandle: HandleEntity? { get } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/PSAUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/PSAUseCase.swift index 4513fe018e..c5b7dc85d8 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/PSAUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/PSAUseCase.swift @@ -1,4 +1,3 @@ - public protocol PSAUseCaseProtocol { func getPSA(completion: @escaping (Result) -> Void) func markAsSeenForPSA(withIdentifier identifier: PSAIdentifier) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/SMS/CheckSMSUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/SMS/CheckSMSUseCase.swift index ab86ee28cd..8ad8a143d9 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/SMS/CheckSMSUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/SMS/CheckSMSUseCase.swift @@ -1,4 +1,3 @@ - public protocol CheckSMSUseCaseProtocol { func checkVerificationCode(_ code: String, completion: @escaping (Result) -> Void) func sendVerification(toPhoneNumber: String, completion: @escaping (Result) -> Void) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/SMS/SMSUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/SMS/SMSUseCase.swift index 8815d8852e..8f8fe58395 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/SMS/SMSUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Account/SMS/SMSUseCase.swift @@ -1,4 +1,3 @@ - public struct SMSUseCase { public let getSMSUseCase: any GetSMSUseCaseProtocol public let checkSMSUseCase: any CheckSMSUseCaseProtocol diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Ads/AdsUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Ads/AdsUseCase.swift new file mode 100644 index 0000000000..2e13409238 --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Ads/AdsUseCase.swift @@ -0,0 +1,24 @@ +public protocol AdsUseCaseProtocol { + func fetchAds(adsFlag: AdsFlagEntity, + adUnits: [AdsSlotEntity], + publicHandle: HandleEntity) async throws -> [String: String] + func queryAds(adsFlag: AdsFlagEntity, publicHandle: HandleEntity) async throws -> Int +} + +public struct AdsUseCase: AdsUseCaseProtocol { + private let repository: T + + public init(repository: T) { + self.repository = repository + } + + public func fetchAds(adsFlag: AdsFlagEntity, + adUnits: [AdsSlotEntity], + publicHandle: HandleEntity = .invalid) async throws -> [String: String] { + try await repository.fetchAds(adsFlag: adsFlag, adUnits: adUnits, publicHandle: publicHandle) + } + + public func queryAds(adsFlag: AdsFlagEntity, publicHandle: HandleEntity = .invalid) async throws -> Int { + try await repository.queryAds(adsFlag: adsFlag, publicHandle: publicHandle) + } +} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Album/AlbumListUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Album/AlbumListUseCase.swift index 125d4aeb38..3e34cec11f 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Album/AlbumListUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Album/AlbumListUseCase.swift @@ -75,7 +75,8 @@ public struct AlbumListUseCase AlbumMetaDataEntity { + let counts = albumContent + .reduce(into: (image: 0, video: 0)) { (result, content) in + guard let mediaType = content.photo.mediaType else { return } + switch mediaType { + case .image: + result.image += 1 + case .video: + result.video += 1 + } + } + + return AlbumMetaDataEntity(imageCount: counts.image, + videoCount: counts.video) + } + public func hasNoPhotosAndVideos() async -> Bool { let allPhotos = try? await allPhotos() let allVideos = try? await allVideos() diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Analytics/AnalyticsEventUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Analytics/AnalyticsEventUseCase.swift index 2462f79638..82c3b1735d 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Analytics/AnalyticsEventUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Analytics/AnalyticsEventUseCase.swift @@ -1,4 +1,3 @@ - public protocol AnalyticsEventUseCaseProtocol { func sendAnalyticsEvent(_ event: AnalyticsEventEntity) } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/AudioVideo/AudioSessionUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/AudioVideo/AudioSessionUseCase.swift index cb16dfd2fb..65a7d96069 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/AudioVideo/AudioSessionUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/AudioVideo/AudioSessionUseCase.swift @@ -1,4 +1,3 @@ - public protocol AudioSessionUseCaseProtocol { var isBluetoothAudioRouteAvailable: Bool { get } var currentSelectedAudioPort: AudioPort { get } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/AudioVideo/CaptureDeviceUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/AudioVideo/CaptureDeviceUseCase.swift index b9957f90e2..a3a2937033 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/AudioVideo/CaptureDeviceUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/AudioVideo/CaptureDeviceUseCase.swift @@ -1,6 +1,5 @@ - public protocol CaptureDeviceUseCaseProtocol { - func wideAngleCameraLocalizedName(postion: CameraPositionEntity) -> String? + func wideAngleCameraLocalizedName(position: CameraPositionEntity) -> String? } public struct CaptureDeviceUseCase: CaptureDeviceUseCaseProtocol { @@ -10,7 +9,7 @@ public struct CaptureDeviceUseCase: CaptureD self.repo = repo } - public func wideAngleCameraLocalizedName(postion: CameraPositionEntity) -> String? { - repo.wideAngleCameraLocalizedName(postion: postion) + public func wideAngleCameraLocalizedName(position: CameraPositionEntity) -> String? { + repo.wideAngleCameraLocalizedName(position: position) } } diff --git a/iMEGA/Home/UseCase/Banner/UserBannerUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Banner/UserBannerUseCase.swift similarity index 57% rename from iMEGA/Home/UseCase/Banner/UserBannerUseCase.swift rename to Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Banner/UserBannerUseCase.swift index 8d678a9794..2973c72c5e 100644 --- a/iMEGA/Home/UseCase/Banner/UserBannerUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Banner/UserBannerUseCase.swift @@ -1,4 +1,4 @@ -protocol UserBannerUseCaseProtocol { +public protocol UserBannerUseCaseProtocol { func banners(completion: @escaping (Result<[BannerEntity], BannerErrorEntity>) -> Void) @@ -8,22 +8,26 @@ protocol UserBannerUseCaseProtocol { func bannerCategory(withBannerId bannerId: Int) -> UserBannerUseCase.BannerCategory } -struct UserBannerUseCase: UserBannerUseCaseProtocol { +public struct UserBannerUseCase: UserBannerUseCaseProtocol { let userBannerRepository: any BannerRepositoryProtocol + + public init(userBannerRepository: any BannerRepositoryProtocol) { + self.userBannerRepository = userBannerRepository + } // MARK: - UserBannerUseCaseProtocol - func banners(completion: @escaping (Result<[BannerEntity], BannerErrorEntity>) -> Void) { + public func banners(completion: @escaping (Result<[BannerEntity], BannerErrorEntity>) -> Void) { userBannerRepository.banners(completion: completion) } - func dismissBanner(withBannerId bannerId: Int, - completion: ((Result) -> Void)?) { + public func dismissBanner(withBannerId bannerId: Int, + completion: ((Result) -> Void)?) { userBannerRepository.dismissBanner(withBannerId: bannerId, completion: completion) } - func bannerCategory(withBannerId bannerId: Int) -> BannerCategory { + public func bannerCategory(withBannerId bannerId: Int) -> BannerCategory { switch bannerId { case 1: return .achievement case 2: return .referal @@ -32,7 +36,7 @@ struct UserBannerUseCase: UserBannerUseCaseProtocol { } // MARK: - - enum BannerCategory { + public enum BannerCategory { case referal case achievement case undefined diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatLinkErrorEntity.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatLinkErrorEntity.swift index d9be8bca0b..246e38d251 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatLinkErrorEntity.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatLinkErrorEntity.swift @@ -1,4 +1,3 @@ - public enum ChatLinkErrorEntity: Error { case generic case resourceNotFound diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatRoomUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatRoomUseCase.swift index c441eb8edf..5552241483 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatRoomUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatRoomUseCase.swift @@ -31,6 +31,7 @@ public protocol ChatRoomUseCaseProtocol { func chatMessageLoaded(forChatRoom chatRoom: ChatRoomEntity) -> AnyPublisher func closeChatRoom(_ chatRoom: ChatRoomEntity) func hasScheduledMeetingChange(_ change: ChatMessageScheduledMeetingChangeType, for message: ChatMessageEntity, inChatRoom chatRoom: ChatRoomEntity) -> Bool + func shouldOpenWaitingRoom(forChatId chatId: HandleEntity) -> Bool } public struct ChatRoomUseCase: ChatRoomUseCaseProtocol { @@ -190,4 +191,10 @@ public struct ChatRoomUseCase: ChatRoomUseCasePro public func hasScheduledMeetingChange(_ change: ChatMessageScheduledMeetingChangeType, for message: ChatMessageEntity, inChatRoom chatRoom: ChatRoomEntity) -> Bool { chatRoomRepo.hasScheduledMeetingChange(change, for: message, inChatRoom: chatRoom) } + + public func shouldOpenWaitingRoom(forChatId chatId: HandleEntity) -> Bool { + guard let chatRoom = chatRoomRepo.chatRoom(forChatId: chatId) else { return false } + let isModerator = chatRoom.ownPrivilege == .moderator + return !isModerator && chatRoom.isWaitingRoomEnabled + } } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatUseCase.swift index f3cfff3bfd..0ff95734be 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ChatUseCase.swift @@ -23,6 +23,7 @@ public protocol ChatUseCaseProtocol { func monitorChatConnectionStatusUpdate(forChatId chatId: HandleEntity) -> AnyPublisher func monitorChatPrivateModeUpdate(forChatId chatId: ChatIdEntity) -> AnyPublisher func chatCall(for chatId: HandleEntity) async -> CallEntity? + func isCallActive(for chatId: HandleEntity) -> Bool } // MARK: - Use case implementation - @@ -116,4 +117,8 @@ public struct ChatUseCase: ChatUseCaseProtocol { public func chatCall(for chatId: HandleEntity) async -> CallEntity? { chatRepo.chatCall(for: chatId) } + + public func isCallActive(for chatId: HandleEntity) -> Bool { + chatRepo.isCallActive(for: chatId) + } } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ClearChatHistoryUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ClearChatHistoryUseCase.swift index 391434423e..221bec7abb 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ClearChatHistoryUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ClearChatHistoryUseCase.swift @@ -1,4 +1,3 @@ - public protocol ClearChatHistoryUseCaseProtocol { func clearChatHistory(for chatId: ChatIdEntity, completion: @escaping (Result) -> Void) } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/HistoryRetentionUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/HistoryRetentionUseCase.swift index f825378cbb..de8d9272d5 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/HistoryRetentionUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/HistoryRetentionUseCase.swift @@ -1,4 +1,3 @@ - public protocol HistoryRetentionUseCaseProtocol { func chatRetentionTime(for chatId: ChatIdEntity, completion: @escaping (Result) -> Void) func setChatRetentionTime(for chatId: ChatIdEntity, period: UInt, completion: @escaping (Result) -> Void) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ManageChatHistoryUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ManageChatHistoryUseCase.swift index ec9ef92220..1b737b0e38 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ManageChatHistoryUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/ManageChatHistoryUseCase.swift @@ -1,4 +1,3 @@ - public struct ManageChatHistoryUseCase { public let retentionValueUseCase: any HistoryRetentionUseCaseProtocol public let historyRetentionUseCase: any HistoryRetentionUseCaseProtocol diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/WaitingRoomUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/WaitingRoomUseCase.swift new file mode 100644 index 0000000000..5d2035f060 --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Chat/WaitingRoomUseCase.swift @@ -0,0 +1,15 @@ +public protocol WaitingRoomUseCaseProtocol { + func userName() -> String +} + +public final class WaitingRoomUseCase: WaitingRoomUseCaseProtocol { + private var waitingRoomRepo: T + + public init(waitingRoomRepo: T) { + self.waitingRoomRepo = waitingRoomRepo + } + + public func userName() -> String { + waitingRoomRepo.userName() + } +} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Contact/ContactsUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Contact/ContactsUseCase.swift index 7b1e8e3730..041b09e998 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Contact/ContactsUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Contact/ContactsUseCase.swift @@ -1,4 +1,3 @@ - // MARK: - Use case protocol - public protocol ContactsUseCaseProtocol { var isAuthorizedToAccessPhoneContacts: Bool { get } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Credentials/CredentialUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Credentials/CredentialUseCase.swift index e5b0292005..0d13ee950c 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Credentials/CredentialUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Credentials/CredentialUseCase.swift @@ -1,4 +1,3 @@ - public protocol CredentialUseCaseProtocol { func hasSession() -> Bool func isPasscodeEnabled() -> Bool diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/FeatureFlag/FeatureFlagUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/FeatureFlag/FeatureFlagUseCase.swift index b634784d37..49b2376405 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/FeatureFlag/FeatureFlagUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/FeatureFlag/FeatureFlagUseCase.swift @@ -1,4 +1,3 @@ - public protocol FeatureFlagUseCaseProtocol { func savedFeatureFlags() -> [FeatureFlagEntity] func isFeatureFlagEnabled(for key: FeatureFlagName) -> Bool diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/MEGASdk/MEGAHandleUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/MEGASdk/MEGAHandleUseCase.swift index a6b9993edb..40c67d60e7 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/MEGASdk/MEGAHandleUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/MEGASdk/MEGAHandleUseCase.swift @@ -1,4 +1,3 @@ - public protocol MEGAHandleUseCaseProtocol { func base64Handle(forUserHandle handle: HandleEntity) -> Base64HandleEntity? func handle(forBase64Handle handle: Base64HandleEntity) -> HandleEntity? diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Media/VideoMediaUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Media/VideoMediaUseCase.swift index 730e003916..c873cda14c 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Media/VideoMediaUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Media/VideoMediaUseCase.swift @@ -1,4 +1,3 @@ - public protocol VideoMediaUseCaseProtocol { func isPlayable(_ node: NodeEntity) -> Bool } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Network/NetworkMonitorUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Network/NetworkMonitorUseCase.swift index b4b73c1e28..7c93cba2bc 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Network/NetworkMonitorUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Network/NetworkMonitorUseCase.swift @@ -1,4 +1,3 @@ - public protocol NetworkMonitorUseCaseProtocol { func networkPathChanged(completion: @escaping (Bool) -> Void) func isConnected() -> Bool diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/NodeActionUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/NodeActionUseCase.swift index 51b42bdf21..594eaf2ae8 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/NodeActionUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/NodeActionUseCase.swift @@ -1,7 +1,7 @@ public protocol NodeActionUseCaseProtocol { /// Fetch the filesystem in MEGA - func fetchnodes() async throws + func fetchNodes() async throws /// Create a folder in the MEGA account /// - Parameters: @@ -51,8 +51,8 @@ public struct NodeActionUseCase: NodeActionUseC self.repo = repo } - public func fetchnodes() async throws { - try await repo.fetchnodes() + public func fetchNodes() async throws { + try await repo.fetchNodes() } public func createFolder(name: String, parent: NodeEntity) async throws -> NodeEntity { diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/NodeAttributeUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/NodeAttributeUseCase.swift index 1e4ae5209a..4da5c1bb09 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/NodeAttributeUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/NodeAttributeUseCase.swift @@ -1,4 +1,3 @@ - public protocol NodeAttributeUseCaseProtocol { func pathFor(node: NodeEntity) -> String? func numberChildrenFor(node: NodeEntity) -> Int diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/SaveMediaToPhotosUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/SaveMediaToPhotosUseCase.swift index 3c463f2f10..8b1eed241a 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/SaveMediaToPhotosUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Node/SaveMediaToPhotosUseCase.swift @@ -1,4 +1,3 @@ - public protocol SaveMediaToPhotosUseCaseProtocol { func saveToPhotos(nodes: [NodeEntity]) async throws func saveToPhotosChatNode(handle: HandleEntity, messageId: HandleEntity, chatId: HandleEntity, completion: @escaping (Result) -> Void) @@ -21,31 +20,15 @@ public struct SaveMediaToPhotosUseCase: PreferenceUseC repo = repository } - public subscript(key: PreferenceKeyEntity) -> T? { + public subscript(key: PreferenceKeyEntity) -> V? { get { repo[key.rawValue] } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/APIEnvironmentUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/APIEnvironmentUseCase.swift index 381cf96c67..a89a3b8b60 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/APIEnvironmentUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/APIEnvironmentUseCase.swift @@ -1,4 +1,3 @@ - public protocol APIEnvironmentUseCaseProtocol { func changeAPIURL(_ environment: APIEnvironmentEntity) } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/AppEnvironmentUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/AppEnvironmentUseCase.swift index 0ece8a200b..e9aa658dfb 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/AppEnvironmentUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/AppEnvironmentUseCase.swift @@ -1,4 +1,3 @@ - public protocol AppEnvironmentUseCaseProtocol { var configuration: AppConfigurationEntity { get } func config(_ configuration: AppConfigurationEntity) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/ManageLogsUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/ManageLogsUseCase.swift index 2255481ca7..2820834e0b 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/ManageLogsUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Settings/ManageLogsUseCase.swift @@ -1,4 +1,3 @@ - public protocol ManageLogsUseCaseProtocol { func toggleLogs() } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Transfer/UploadPhotoAssetsUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Transfer/UploadPhotoAssetsUseCase.swift index fd5cbf1fec..748fefaf68 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Transfer/UploadPhotoAssetsUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Transfer/UploadPhotoAssetsUseCase.swift @@ -1,4 +1,3 @@ - public protocol UploadPhotoAssetsUseCaseProtocol { /// Upload from photo albums diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/User/UserInviteUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/User/UserInviteUseCase.swift index dd7f390685..ec643f75f1 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/User/UserInviteUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/User/UserInviteUseCase.swift @@ -1,7 +1,5 @@ - public protocol UserInviteUseCaseProtocol { - func sendInvite(forEmail email: String, - completion: @escaping (Result) -> Void) + func sendInvite(forEmail email: String) async throws } public struct UserInviteUseCase: UserInviteUseCaseProtocol { @@ -11,7 +9,7 @@ public struct UserInviteUseCase: UserInviteUseC self.repo = repo } - public func sendInvite(forEmail email: String, completion: @escaping (Result) -> Void) { - repo.sendInvite(forEmail: email, completion: completion) + public func sendInvite(forEmail email: String) async throws { + try await repo.sendInvite(forEmail: email) } } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Versions/FileVersionsUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Versions/FileVersionsUseCase.swift index 3e0bbb7082..9905dcb122 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Versions/FileVersionsUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Versions/FileVersionsUseCase.swift @@ -1,4 +1,3 @@ - public protocol FileVersionsUseCaseProtocol { func isFileVersionsEnabled(completion: @escaping (Result) -> Void) func enableFileVersions(_ enable: Bool, completion: @escaping (Result) -> Void) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/Entity/AlbumEntity+Test.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/Entity/AlbumEntity+Test.swift index 88ef1355b4..a0d5f2d4bd 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/Entity/AlbumEntity+Test.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/Entity/AlbumEntity+Test.swift @@ -10,7 +10,18 @@ public extension AlbumEntity { creationTime: Date? = nil, modificationTime: Date? = nil, sharedLinkStatus: SharedLinkStatusEntity = .unavailable, + metaData: AlbumMetaDataEntity? = nil, isTesting: Bool = true) { - self.init(id: id, name: name, coverNode: coverNode, count: count, type: type, creationTime: creationTime, modificationTime: modificationTime, sharedLinkStatus: sharedLinkStatus) + self.init( + id: id, + name: name, + coverNode: coverNode, + count: count, + type: type, + creationTime: creationTime, + modificationTime: modificationTime, + sharedLinkStatus: sharedLinkStatus, + metaData: metaData + ) } } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/Entity/CallEntity+Test.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/Entity/CallEntity+Test.swift index 394ef47fe6..7895caf65d 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/Entity/CallEntity+Test.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/Entity/CallEntity+Test.swift @@ -20,7 +20,10 @@ public extension CallEntity { sessionClientIds: [HandleEntity] = [], clientSessions: [ChatSessionEntity] = [], participants: [HandleEntity] = [], - uuid: UUID = UUID(uuidString: "45adcd56-a31c-11eb-bcbc-0242ac130002")!) { - self.init(status: status, chatId: chatId, callId: callId, changeTye: changeType, duration: duration, initialTimestamp: initialTimestamp, finalTimestamp: finalTimestamp, hasLocalAudio: hasLocalAudio, hasLocalVideo: hasLocalVideo, termCodeType: termCodeType, isRinging: isRinging, callCompositionChange: callCompositionChange, numberOfParticipants: numberOfParticipants, isOnHold: isOnHold, sessionClientIds: sessionClientIds, clientSessions: clientSessions, participants: participants, uuid: uuid) + waitingRoom: WaitingRoomEntity? = nil, + waitingRoomHandleList: [HandleEntity] = [], + uuid: UUID = UUID(uuidString: "45adcd56-a31c-11eb-bcbc-0242ac130002")!, + isTesting: Bool = true) { + self.init(status: status, chatId: chatId, callId: callId, changeType: changeType, duration: duration, initialTimestamp: initialTimestamp, finalTimestamp: finalTimestamp, hasLocalAudio: hasLocalAudio, hasLocalVideo: hasLocalVideo, termCodeType: termCodeType, isRinging: isRinging, callCompositionChange: callCompositionChange, numberOfParticipants: numberOfParticipants, isOnHold: isOnHold, sessionClientIds: sessionClientIds, clientSessions: clientSessions, participants: participants, waitingRoomStatus: .unknown, waitingRoom: waitingRoom, waitingRoomHandleList: waitingRoomHandleList, uuid: uuid) } } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockAdsRepository.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockAdsRepository.swift new file mode 100644 index 0000000000..2d5b0c564c --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockAdsRepository.swift @@ -0,0 +1,23 @@ +import MEGADomain + +public final class MockAdsRepository: AdsRepositoryProtocol { + private let adsValue: [String: String] + private let queryAdsValue: Int + + public static var newRepo: MockAdsRepository { + MockAdsRepository() + } + + public init(adsValue: [String: String] = [:], queryAdsValue: Int = 0) { + self.adsValue = adsValue + self.queryAdsValue = queryAdsValue + } + + public func fetchAds(adsFlag: AdsFlagEntity, adUnits: [AdsSlotEntity], publicHandle: HandleEntity) async throws -> [String: String] { + adsValue + } + + public func queryAds(adsFlag: AdsFlagEntity, publicHandle: HandleEntity) async throws -> Int { + queryAdsValue + } +} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockChatRoomRepository.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockChatRoomRepository.swift new file mode 100644 index 0000000000..33291f450c --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockChatRoomRepository.swift @@ -0,0 +1,195 @@ +import Combine +import MEGADomain + +public final class MockChatRoomRepository: ChatRoomRepositoryProtocol { + public static var newRepo: MockChatRoomRepository = MockChatRoomRepository() + + private let chatRoom: ChatRoomEntity? + private let peerHandles: [MEGADomain.HandleEntity] + private let peerPrivilege: MEGADomain.ChatRoomPrivilegeEntity? + private let userStatus: MEGADomain.ChatStatusEntity + private let createChatRoomResult: Result + private let createPublicLinkResult: Result + private let queryChatLinkResult: Result + private let renameChatRoomResult: Result + private let base64Handle: String? + private let participantsUpdatedPublisher: AnyPublisher<[MEGADomain.HandleEntity], Never> + private let userPrivilegeChangedPublisher: AnyPublisher + private let ownPrivilegeChangedPublisher: AnyPublisher + private let allowNonHostToAddParticipantsValueChangedPublisher: AnyPublisher + private let waitingRoomValueChangedPublisher: AnyPublisher + private let message: MEGADomain.ChatMessageEntity? + private let isChatRoomOpen: Bool + private let leaveChatRoom: Bool + private let loadMessages: MEGADomain.ChatSourceEntity + private let chatRoomMessageLoadedPublisher: AnyPublisher + private let hasScheduledMeetingChange: Bool + + public init( + chatRoom: ChatRoomEntity? = nil, + peerHandles: [MEGADomain.HandleEntity] = [], + peerPrivilege: MEGADomain.ChatRoomPrivilegeEntity? = nil, + userStatus: MEGADomain.ChatStatusEntity = .online, + createChatRoomResult: Result = .failure(.generic), + createPublicLinkResult: Result = .failure(.generic), + queryChatLinkResult: Result = .failure(.generic), + renameChatRoomResult: Result = .failure(.generic), + base64Handle: String? = nil, + participantsUpdatedPublisher: AnyPublisher<[MEGADomain.HandleEntity], Never> = Empty().eraseToAnyPublisher(), + userPrivilegeChangedPublisher: AnyPublisher = Empty().eraseToAnyPublisher(), + ownPrivilegeChangedPublisher: AnyPublisher = Empty().eraseToAnyPublisher(), + allowNonHostToAddParticipantsValueChangedPublisher: AnyPublisher = Empty().eraseToAnyPublisher(), + waitingRoomValueChangedPublisher: AnyPublisher = Empty().eraseToAnyPublisher(), + message: MEGADomain.ChatMessageEntity? = nil, + isChatRoomOpen: Bool = true, + leaveChatRoom: Bool = true, + loadMessages: MEGADomain.ChatSourceEntity = .local, + chatRoomMessageLoadedPublisher: AnyPublisher = Empty().eraseToAnyPublisher(), + hasScheduledMeetingChange: Bool = false + ) { + self.chatRoom = chatRoom + self.peerHandles = peerHandles + self.peerPrivilege = peerPrivilege + self.userStatus = userStatus + self.createChatRoomResult = createChatRoomResult + self.createPublicLinkResult = createPublicLinkResult + self.queryChatLinkResult = queryChatLinkResult + self.renameChatRoomResult = renameChatRoomResult + self.base64Handle = base64Handle + self.participantsUpdatedPublisher = participantsUpdatedPublisher + self.userPrivilegeChangedPublisher = userPrivilegeChangedPublisher + self.ownPrivilegeChangedPublisher = ownPrivilegeChangedPublisher + self.allowNonHostToAddParticipantsValueChangedPublisher = allowNonHostToAddParticipantsValueChangedPublisher + self.waitingRoomValueChangedPublisher = waitingRoomValueChangedPublisher + self.message = message + self.isChatRoomOpen = isChatRoomOpen + self.leaveChatRoom = leaveChatRoom + self.loadMessages = loadMessages + self.chatRoomMessageLoadedPublisher = chatRoomMessageLoadedPublisher + self.hasScheduledMeetingChange = hasScheduledMeetingChange + } + + public func chatRoom(forChatId chatId: MEGADomain.HandleEntity) -> MEGADomain.ChatRoomEntity? { + chatRoom + } + + public func chatRoom(forUserHandle userHandle: MEGADomain.HandleEntity) -> MEGADomain.ChatRoomEntity? { + chatRoom + } + + public func peerHandles(forChatRoom chatRoom: MEGADomain.ChatRoomEntity) -> [MEGADomain.HandleEntity] { + peerHandles + } + + public func peerPrivilege(forUserHandle userHandle: MEGADomain.HandleEntity, chatRoom: MEGADomain.ChatRoomEntity) -> MEGADomain.ChatRoomPrivilegeEntity? { + peerPrivilege + } + + public func userStatus(forUserHandle userHandle: MEGADomain.HandleEntity) -> MEGADomain.ChatStatusEntity { + userStatus + } + + public func createChatRoom(forUserHandle userHandle: MEGADomain.HandleEntity, completion: @escaping (Result) -> Void) { + completion(createChatRoomResult) + } + + public func createPublicLink(forChatRoom chatRoom: MEGADomain.ChatRoomEntity, completion: @escaping (Result) -> Void) { + completion(createPublicLinkResult) + } + + public func queryChatLink(forChatRoom chatRoom: MEGADomain.ChatRoomEntity, completion: @escaping (Result) -> Void) { + completion(queryChatLinkResult) + } + + public func renameChatRoom(_ chatRoom: MEGADomain.ChatRoomEntity, title: String, completion: @escaping (Result) -> Void) { + completion(renameChatRoomResult) + } + + public func renameChatRoom(_ chatRoom: MEGADomain.ChatRoomEntity, title: String) async throws -> String { + title + } + + public func archive(_ archive: Bool, chatRoom: MEGADomain.ChatRoomEntity) { + } + + public func archive(_ archive: Bool, chatRoom: MEGADomain.ChatRoomEntity) async throws -> Bool { + archive + } + + public func setMessageSeenForChat(forChatRoom chatRoom: MEGADomain.ChatRoomEntity, messageId: MEGADomain.HandleEntity) { + } + + public func base64Handle(forChatRoom chatRoom: MEGADomain.ChatRoomEntity) -> String? { + base64Handle + } + + public func allowNonHostToAddParticipants(_ enabled: Bool, forChatRoom chatRoom: MEGADomain.ChatRoomEntity) async throws -> Bool { + enabled + } + + public func waitingRoom(_ enabled: Bool, forChatRoom chatRoom: MEGADomain.ChatRoomEntity) async throws -> Bool { + enabled + } + + public func participantsUpdated(forChatRoom chatRoom: MEGADomain.ChatRoomEntity) -> AnyPublisher<[MEGADomain.HandleEntity], Never> { + participantsUpdatedPublisher + } + + public func userPrivilegeChanged(forChatRoom chatRoom: MEGADomain.ChatRoomEntity) -> AnyPublisher { + userPrivilegeChangedPublisher + } + + public func ownPrivilegeChanged(forChatRoom chatRoom: MEGADomain.ChatRoomEntity) -> AnyPublisher { + ownPrivilegeChangedPublisher + } + + public func allowNonHostToAddParticipantsValueChanged(forChatRoom chatRoom: MEGADomain.ChatRoomEntity) -> AnyPublisher { + allowNonHostToAddParticipantsValueChangedPublisher + } + + public func waitingRoomValueChanged(forChatRoom chatRoom: MEGADomain.ChatRoomEntity) -> AnyPublisher { + waitingRoomValueChangedPublisher + } + + public func message(forChatRoom chatRoom: MEGADomain.ChatRoomEntity, messageId: MEGADomain.HandleEntity) -> MEGADomain.ChatMessageEntity? { + message + } + + public func isChatRoomOpen(_ chatRoom: MEGADomain.ChatRoomEntity) -> Bool { + isChatRoomOpen + } + + public func openChatRoom(_ chatRoom: MEGADomain.ChatRoomEntity, delegate: MEGADomain.ChatRoomDelegateEntity) throws { + } + + public func closeChatRoom(_ chatRoom: MEGADomain.ChatRoomEntity, delegate: MEGADomain.ChatRoomDelegateEntity) { + } + + public func closeChatRoomPreview(chatRoom: MEGADomain.ChatRoomEntity) { + } + + public func leaveChatRoom(chatRoom: MEGADomain.ChatRoomEntity) async -> Bool { + leaveChatRoom + } + + public func updateChatPrivilege(chatRoom: MEGADomain.ChatRoomEntity, userHandle: MEGADomain.HandleEntity, privilege: MEGADomain.ChatRoomPrivilegeEntity) { + } + + public func invite(toChat chat: MEGADomain.ChatRoomEntity, userId: MEGADomain.HandleEntity) { + } + + public func remove(fromChat chat: MEGADomain.ChatRoomEntity, userId: MEGADomain.HandleEntity) { + } + + public func loadMessages(forChat chat: MEGADomain.ChatRoomEntity, count: Int) -> MEGADomain.ChatSourceEntity { + loadMessages + } + + public func chatRoomMessageLoaded(forChatRoom chatRoom: MEGADomain.ChatRoomEntity) -> AnyPublisher { + chatRoomMessageLoadedPublisher + } + + public func hasScheduledMeetingChange(_ change: MEGADomain.ChatMessageScheduledMeetingChangeType, for message: MEGADomain.ChatMessageEntity, inChatRoom chatRoom: MEGADomain.ChatRoomEntity) -> Bool { + hasScheduledMeetingChange + } +} diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockDownloadFileRepository.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockDownloadFileRepository.swift index b044551998..eaeb9accc8 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockDownloadFileRepository.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockDownloadFileRepository.swift @@ -20,6 +20,10 @@ public struct MockDownloadFileRepository: DownloadFileRepositoryProtocol { public func download(nodeHandle: HandleEntity, to url: URL, metaData: TransferMetaDataEntity?, completion: @escaping (Result) -> Void) { completion(completionResult) } + + public func download(nodeHandle: HandleEntity, to url: URL, metaData: TransferMetaDataEntity?) async throws -> TransferEntity { + try await withCheckedThrowingContinuation { continuation in continuation.resume(with: completionResult) } + } public func downloadChat(nodeHandle: HandleEntity, messageId: HandleEntity, chatId: HandleEntity, to url: URL, metaData: TransferMetaDataEntity?, completion: @escaping (Result) -> Void) { completion(completionResult) diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockNodeActionRepository.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockNodeActionRepository.swift index 79671c19a8..0344c70211 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockNodeActionRepository.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockRepos/MockNodeActionRepository.swift @@ -11,7 +11,7 @@ public final class MockNodeActionRepository: NodeActionRepositoryProtocol { self.createFolderResult = createFolderResult } - public func fetchnodes() async throws {} + public func fetchNodes() async throws {} public func createFolder(name: String, parent: NodeEntity) async throws -> NodeEntity { createFolderName = name diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockCaptureDeviceUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockCaptureDeviceUseCase.swift index 9cbe704b8e..c2c363b32d 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockCaptureDeviceUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockCaptureDeviceUseCase.swift @@ -7,7 +7,7 @@ public struct MockCaptureDeviceUseCase: CaptureDeviceUseCaseProtocol { self.cameraPositionName = cameraPositionName } - public func wideAngleCameraLocalizedName(postion: CameraPositionEntity) -> String? { + public func wideAngleCameraLocalizedName(position: CameraPositionEntity) -> String? { return cameraPositionName } } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockChangeSfuServerUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockChangeSfuServerUseCase.swift index b95ba63499..5e52139dcd 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockChangeSfuServerUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockChangeSfuServerUseCase.swift @@ -1,4 +1,3 @@ - import MEGADomain public struct MockChangeSfuServerUseCase: ChangeSfuServerUseCaseProtocol { diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockChatUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockChatUseCase.swift index 86a24b67a9..751a5ab755 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockChatUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockChatUseCase.swift @@ -6,6 +6,7 @@ public final class MockChatUseCase: ChatUseCaseProtocol { public var guestAccount: Bool public var fullName: String? public var status: ChatStatusEntity + public var isExistingActiveCall: Bool public var isCallActive: Bool public var isCallInProgress: Bool public var activeCallEntity: CallEntity? @@ -26,6 +27,7 @@ public final class MockChatUseCase: ChatUseCaseProtocol { isGuestAccount: Bool = false, fullName: String? = nil, status: ChatStatusEntity = .offline, + isExistingActiveCall: Bool = false, isCallActive: Bool = false, isCallInProgress: Bool = false, statusChangePublisher: PassthroughSubject<(HandleEntity, ChatStatusEntity), Never> = PassthroughSubject<(HandleEntity, ChatStatusEntity), Never>(), @@ -40,6 +42,7 @@ public final class MockChatUseCase: ChatUseCaseProtocol { self.guestAccount = isGuestAccount self.fullName = fullName self.status = status + self.isExistingActiveCall = isExistingActiveCall self.isCallActive = isCallActive self.isCallInProgress = isCallInProgress self.statusChangePublisher = statusChangePublisher @@ -76,7 +79,7 @@ public final class MockChatUseCase: ChatUseCaseProtocol { } public func existsActiveCall() -> Bool { - isCallActive + isExistingActiveCall } public func activeCall() -> CallEntity? { @@ -146,4 +149,8 @@ public final class MockChatUseCase: ChatUseCaseProtocol { public func chatConnectionStatus(for chatId: ChatIdEntity) async -> ChatConnectionStatus { currentChatConnectionStatus } + + public func isCallActive(for chatId: MEGADomain.HandleEntity) -> Bool { + isCallActive + } } diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockClearChatHistoryUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockClearChatHistoryUseCase.swift index c28d6ba06f..aef75a2ca5 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockClearChatHistoryUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockClearChatHistoryUseCase.swift @@ -1,4 +1,3 @@ - import MEGADomain public struct MockClearChatHistoryUseCase: ClearChatHistoryUseCaseProtocol { diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockHistoryRetentionUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockHistoryRetentionUseCase.swift index 9987836480..ac68d7ecba 100644 --- a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockHistoryRetentionUseCase.swift +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockHistoryRetentionUseCase.swift @@ -1,4 +1,3 @@ - import MEGADomain public struct MockHistoryRetentionUseCase: HistoryRetentionUseCaseProtocol { diff --git a/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockWaitingRoomUseCase.swift b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockWaitingRoomUseCase.swift new file mode 100644 index 0000000000..7341e48367 --- /dev/null +++ b/Modules/Domain/MEGADomain/Sources/MEGADomainMock/MockUseCases/MockWaitingRoomUseCase.swift @@ -0,0 +1,13 @@ +import MEGADomain + +public final class MockWaitingRoomUseCase: WaitingRoomUseCaseProtocol { + private let myUserName: String + + public init(myUserName: String = "") { + self.myUserName = myUserName + } + + public func userName() -> String { + myUserName + } +} diff --git a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/AccountPlanPurchaseUseCaseTests.swift b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/AccountPlanPurchaseUseCaseTests.swift index a65bd80485..cc1c56accc 100644 --- a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/AccountPlanPurchaseUseCaseTests.swift +++ b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/AccountPlanPurchaseUseCaseTests.swift @@ -32,17 +32,17 @@ final class AccountPlanPurchaseUseCaseTests: XCTestCase { } private var monthlyPlans: [AccountPlanEntity] { - [AccountPlanEntity(type: .proI, term: .monthly), - AccountPlanEntity(type: .proII, term: .monthly), - AccountPlanEntity(type: .proIII, term: .monthly), - AccountPlanEntity(type: .lite, term: .monthly)] + [AccountPlanEntity(type: .proI, subscriptionCycle: .monthly), + AccountPlanEntity(type: .proII, subscriptionCycle: .monthly), + AccountPlanEntity(type: .proIII, subscriptionCycle: .monthly), + AccountPlanEntity(type: .lite, subscriptionCycle: .monthly)] } private var yearlyPlans: [AccountPlanEntity] { - [AccountPlanEntity(type: .proI, term: .yearly), - AccountPlanEntity(type: .proII, term: .yearly), - AccountPlanEntity(type: .proIII, term: .yearly), - AccountPlanEntity(type: .lite, term: .yearly)] + [AccountPlanEntity(type: .proI, subscriptionCycle: .yearly), + AccountPlanEntity(type: .proII, subscriptionCycle: .yearly), + AccountPlanEntity(type: .proIII, subscriptionCycle: .yearly), + AccountPlanEntity(type: .lite, subscriptionCycle: .yearly)] } private var allPlans: [AccountPlanEntity] { diff --git a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/AdsUseCaseTests.swift b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/AdsUseCaseTests.swift new file mode 100644 index 0000000000..412a8cd4e4 --- /dev/null +++ b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/AdsUseCaseTests.swift @@ -0,0 +1,24 @@ +import MEGADomain +import MEGADomainMock +import XCTest + +final class AdsUseCaseTests: XCTestCase { + + func testFetchAds_shouldReturnCorrectAds() async throws { + let expectedAds = ["FILES": "https://testAd/link"] + let sut = AdsUseCase(repository: MockAdsRepository(adsValue: expectedAds)) + + let adsValue = try await sut.fetchAds(adsFlag: .defaultAds, adUnits: [.files]) + + XCTAssertEqual(adsValue, expectedAds) + } + + func testQueryAds_shouldReturnCorrectValue() async throws { + let expectedValue = Int.random(in: 0...1) + let sut = AdsUseCase(repository: MockAdsRepository(queryAdsValue: expectedValue)) + + let queryAdsValue = try await sut.queryAds(adsFlag: .defaultAds) + + XCTAssertEqual(queryAdsValue, expectedValue) + } +} diff --git a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/AlbumListUseCaseTests.swift b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/AlbumListUseCaseTests.swift index ffd2912488..979062724b 100644 --- a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/AlbumListUseCaseTests.swift +++ b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/AlbumListUseCaseTests.swift @@ -112,30 +112,40 @@ final class AlbumListUseCaseTests: XCTestCase { XCTAssertEqual(albums.first?.sharedLinkStatus, .unavailable) } - func testUserAlbums_loadAndRetrieveAlbumCover() async { + func testUserAlbums_load_retrieveAlbumCoverSetCountShareLinkAndMetaData() async throws { let albumId = HandleEntity(1) let albumSetCoverId = HandleEntity(3) let albumCoverNodeId = HandleEntity(3) - let expectedAlbumCover = NodeEntity(handle: albumCoverNodeId) - let expectedAlbums = [ - SetEntity(handle: albumId, userId: HandleEntity(2), coverId: albumSetCoverId, - modificationTime: Date(), name: "Album 1") - ] - let albumElement = SetElementEntity(handle: albumSetCoverId, ownerId: albumId, order: 2, - nodeId: albumCoverNodeId, modificationTime: Date(), name: "Test") - let albumPhotos = [AlbumPhotoEntity(photo: expectedAlbumCover, albumPhotoId: albumSetCoverId), - AlbumPhotoEntity(photo: NodeEntity(name: "Test.jpg", handle: 50))] - let sut = AlbumListUseCase( + let expectedAlbumCover = NodeEntity(handle: albumCoverNodeId, mediaType: .image) + let setEntity = SetEntity(handle: albumId, coverId: albumSetCoverId, + name: "Album 1") + let albumElement = SetElementEntity(handle: albumSetCoverId, ownerId: albumId, + nodeId: albumCoverNodeId, name: "Test") + var albumPhotos = try makeAlbumPhotos() + albumPhotos.append(AlbumPhotoEntity(photo: expectedAlbumCover, albumPhotoId: albumSetCoverId)) + + let sut = makeAlbumListUseCase( fileSearchRepository: MockFilesSearchRepository(photoNodes: [expectedAlbumCover]), - mediaUseCase: MockMediaUseCase(), - userAlbumRepository: MockUserAlbumRepository(albums: expectedAlbums, albumElement: albumElement), - albumContentsUpdateRepository: MockAlbumContentsUpdateNotifierRepository.newRepo, + userAlbumRepository: MockUserAlbumRepository(albums: [setEntity], + albumElement: albumElement), albumContentsUseCase: MockAlbumContentUseCase(photos: albumPhotos)) + let albums = await sut.userAlbums() - XCTAssertEqual(albums.count, expectedAlbums.count) - XCTAssertFalse(albums.contains { $0.type != .user}) - XCTAssertEqual(albums.first?.count, albumPhotos.count) - XCTAssertEqual(albums.first?.coverNode, expectedAlbumCover) + + XCTAssertEqual(albums, [ + AlbumEntity(id: albumId, + name: setEntity.name, + coverNode: expectedAlbumCover, + count: albumPhotos.count, + type: .user, + creationTime: setEntity.creationTime, + modificationTime: setEntity.modificationTime, + sharedLinkStatus: .exported(setEntity.isExported), + metaData: AlbumMetaDataEntity( + imageCount: albumPhotos.count(for: .image), + videoCount: albumPhotos.count(for: .video)) + ) + ]) } func testUserAlbums_loadAlbumWithoutCover_coverIdIsNil() async { @@ -156,32 +166,35 @@ final class AlbumListUseCaseTests: XCTestCase { func testUserAlbum_withInvalidCoverId_shouldUseLatestModifiedAlbumElementAsCover() async throws { let albumId = HandleEntity(1) - let expectedAlbums = [ - SetEntity(handle: albumId, userId: HandleEntity(2), coverId: HandleEntity.invalid, - modificationTime: Date(), name: "Album 1") - ] - let expectedAlbumCoverNode = NodeEntity(name: "Test 4.mov", handle: 4, modificationTime: try "2023-03-01T06:01:04Z".date) - let albumPhotos = [ - AlbumPhotoEntity(photo: NodeEntity(name: "Test 1.jpg", handle: 1, - modificationTime: try "2022-08-18T22:01:04Z".date)), - AlbumPhotoEntity(photo: NodeEntity(name: "Test 2.mp4", handle: 2, - modificationTime: try "2022-08-18T22:01:04Z".date)), - AlbumPhotoEntity(photo: NodeEntity(name: "Test 3.png", handle: 3, - modificationTime: try "2022-08-18T22:01:04Z".date)), - AlbumPhotoEntity(photo: expectedAlbumCoverNode) - ] - let userRepo = MockUserAlbumRepository(albums: expectedAlbums, + let setEntity = SetEntity(handle: albumId, coverId: HandleEntity.invalid, + modificationTime: Date(), name: "Album 1") + let expectedAlbumCoverNode = NodeEntity(name: "Test 4.mov", handle: 4, + modificationTime: try "2023-03-01T06:01:04Z".date, + mediaType: .video) + var albumPhotos = try makeAlbumPhotos() + albumPhotos.append(AlbumPhotoEntity(photo: expectedAlbumCoverNode)) + let userRepo = MockUserAlbumRepository(albums: [setEntity], albumElement: nil) - let sut = AlbumListUseCase( - fileSearchRepository: MockFilesSearchRepository.newRepo, - mediaUseCase: MockMediaUseCase(), + let sut = makeAlbumListUseCase( userAlbumRepository: userRepo, - albumContentsUpdateRepository: MockAlbumContentsUpdateNotifierRepository.newRepo, albumContentsUseCase: MockAlbumContentUseCase(photos: albumPhotos)) + let albums = await sut.userAlbums() - XCTAssertEqual(albums.count, expectedAlbums.count) - XCTAssertEqual(albums.first?.count, albumPhotos.count) - XCTAssertEqual(albums.first?.coverNode, expectedAlbumCoverNode) + + XCTAssertEqual(albums, [ + AlbumEntity(id: albumId, + name: setEntity.name, + coverNode: expectedAlbumCoverNode, + count: albumPhotos.count, + type: .user, + creationTime: setEntity.creationTime, + modificationTime: setEntity.modificationTime, + sharedLinkStatus: .exported(setEntity.isExported), + metaData: AlbumMetaDataEntity( + imageCount: albumPhotos.count(for: .image), + videoCount: albumPhotos.count(for: .video)) + ) + ]) } func testUserAlbum_onSetExported_verifySharedLinkStatusExportedIsSetCorrectly() async { @@ -289,4 +302,39 @@ final class AlbumListUseCaseTests: XCTestCase { modificationTime: Date(), name: "Test")]) wait(for: [exp], timeout: 1.0) } + + // MARK: - Helpers + + private func makeAlbumListUseCase( + fileSearchRepository: some FilesSearchRepositoryProtocol = MockFilesSearchRepository(), + mediaUseCase: some MediaUseCaseProtocol = MockMediaUseCase(), + userAlbumRepository: some UserAlbumRepositoryProtocol = MockUserAlbumRepository(), + albumContentsUpdateRepository: some AlbumContentsUpdateNotifierRepositoryProtocol = MockAlbumContentsUpdateNotifierRepository(), + albumContentsUseCase: some AlbumContentsUseCaseProtocol = MockAlbumContentUseCase() + ) -> some AlbumListUseCaseProtocol { + AlbumListUseCase(fileSearchRepository: fileSearchRepository, + mediaUseCase: mediaUseCase, + userAlbumRepository: userAlbumRepository, + albumContentsUpdateRepository: albumContentsUpdateRepository, + albumContentsUseCase: albumContentsUseCase) + } + + private func makeAlbumPhotos() throws -> [AlbumPhotoEntity] { + [AlbumPhotoEntity(photo: NodeEntity(name: "Test 1.jpg", handle: 1, + modificationTime: try "2022-08-18T22:01:04Z".date, + mediaType: .image)), + AlbumPhotoEntity(photo: NodeEntity(name: "Test 2.mp4", handle: 2, + modificationTime: try "2022-08-18T22:01:04Z".date, + mediaType: .video)), + AlbumPhotoEntity(photo: NodeEntity(name: "Test 3.png", handle: 3, + modificationTime: try "2022-08-18T22:01:04Z".date, + mediaType: .image)) + ] + } +} + +private extension Sequence where Element == AlbumPhotoEntity { + func count(for mediaType: MediaTypeEntity) -> Int { + filter({ $0.photo.mediaType == mediaType }).count + } } diff --git a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/ChatRoomUseCaseTests.swift b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/ChatRoomUseCaseTests.swift new file mode 100644 index 0000000000..85d071780d --- /dev/null +++ b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/ChatRoomUseCaseTests.swift @@ -0,0 +1,29 @@ +import MEGADomain +import MEGADomainMock +import XCTest + +final class ChatRoomUseCaseTests: XCTestCase { + func testShouldOpenWaitingRoom_isNotModeratorAndWaitingRoomEnabled_shouldBeTrue() { + let chatRoom = ChatRoomEntity(ownPrivilege: .standard, isWaitingRoomEnabled: true) + let chatRoomRepository = MockChatRoomRepository(chatRoom: chatRoom) + let sut = ChatRoomUseCase(chatRoomRepo: chatRoomRepository) + + XCTAssertTrue(sut.shouldOpenWaitingRoom(forChatId: HandleEntity())) + } + + func testShouldOpenWaitingRoom_isModeratorAndWaitingRoomEnabled_shouldBeFalse() { + let chatRoom = ChatRoomEntity(ownPrivilege: .moderator, isWaitingRoomEnabled: true) + let chatRoomRepository = MockChatRoomRepository(chatRoom: chatRoom) + let sut = ChatRoomUseCase(chatRoomRepo: chatRoomRepository) + + XCTAssertFalse(sut.shouldOpenWaitingRoom(forChatId: HandleEntity())) + } + + func testShouldOpenWaitingRoom_isNotModeratorAndWaitingRoomNotEnabled_shouldBeFalse() { + let chatRoom = ChatRoomEntity(ownPrivilege: .standard, isWaitingRoomEnabled: false) + let chatRoomRepository = MockChatRoomRepository(chatRoom: chatRoom) + let sut = ChatRoomUseCase(chatRoomRepo: chatRoomRepository) + + XCTAssertFalse(sut.shouldOpenWaitingRoom(forChatId: HandleEntity())) + } +} diff --git a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/ClearChatHistoryUseCaseTests.swift b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/ClearChatHistoryUseCaseTests.swift index 03afd0792f..d35cac8cfa 100644 --- a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/ClearChatHistoryUseCaseTests.swift +++ b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/ClearChatHistoryUseCaseTests.swift @@ -1,4 +1,3 @@ - import MEGADomain import MEGADomainMock import XCTest diff --git a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/CookieSettingsUseCaseTests.swift b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/CookieSettingsUseCaseTests.swift index e86e3b389e..507f5fd150 100644 --- a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/CookieSettingsUseCaseTests.swift +++ b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/CookieSettingsUseCaseTests.swift @@ -1,4 +1,3 @@ - import MEGADomain import MEGADomainMock import XCTest diff --git a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/FileVersionsUseCaseTests.swift b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/FileVersionsUseCaseTests.swift index 7d5e2aa1b8..84692374b5 100644 --- a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/FileVersionsUseCaseTests.swift +++ b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/FileVersionsUseCaseTests.swift @@ -1,4 +1,3 @@ - import MEGADomain import MEGADomainMock import XCTest diff --git a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NetworkMonitorUseCaseTests.swift b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NetworkMonitorUseCaseTests.swift index e16e59b0b7..6e3484b3df 100644 --- a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NetworkMonitorUseCaseTests.swift +++ b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NetworkMonitorUseCaseTests.swift @@ -1,4 +1,3 @@ - import MEGADomain import MEGADomainMock import XCTest diff --git a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NodeActionUseCaseTests.swift b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NodeActionUseCaseTests.swift index ab881203fa..7be490d5a2 100644 --- a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NodeActionUseCaseTests.swift +++ b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NodeActionUseCaseTests.swift @@ -1,4 +1,3 @@ - import MEGADomain import MEGADomainMock import XCTest diff --git a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NodeAttributeUseCaseTests.swift b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NodeAttributeUseCaseTests.swift index ac9d80a26b..2c3d2f943d 100644 --- a/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NodeAttributeUseCaseTests.swift +++ b/Modules/Domain/MEGADomain/Tests/MEGADomainTests/NodeAttributeUseCaseTests.swift @@ -1,4 +1,3 @@ - import MEGADomain import MEGADomainMock import XCTest diff --git a/Modules/Domain/MEGAIntentDomain/.gitignore b/Modules/Domain/MEGAIntentDomain/.gitignore new file mode 100644 index 0000000000..3b29812086 --- /dev/null +++ b/Modules/Domain/MEGAIntentDomain/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Modules/Domain/MEGAIntentDomain/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Modules/Domain/MEGAIntentDomain/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/Modules/Domain/MEGAIntentDomain/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Modules/Domain/MEGAIntentDomain/Package.swift b/Modules/Domain/MEGAIntentDomain/Package.swift new file mode 100644 index 0000000000..1e4b307a7d --- /dev/null +++ b/Modules/Domain/MEGAIntentDomain/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version: 5.8 + +import PackageDescription + +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + +let package = Package( + name: "MEGAIntentDomain", + platforms: [ + .macOS(.v10_15), .iOS(.v14) + ], + products: [ + .library( + name: "MEGAIntentDomain", + targets: ["MEGAIntentDomain"]) + ], + dependencies: [ + .package(path: "../../Domain/MEGADomain") + ], + targets: [ + .target( + name: "MEGAIntentDomain", + dependencies: ["MEGADomain"], + swiftSettings: settings), + .testTarget( + name: "MEGAIntentDomainTests", + dependencies: ["MEGADomain", "MEGAIntentDomain"], + swiftSettings: settings) + ] +) diff --git a/Modules/Domain/MEGAIntentDomain/README.md b/Modules/Domain/MEGAIntentDomain/README.md new file mode 100644 index 0000000000..4d44b20649 --- /dev/null +++ b/Modules/Domain/MEGAIntentDomain/README.md @@ -0,0 +1,3 @@ +# MEGAIntentDomain + +Module for MEGA's Intent extension domain logic diff --git a/Modules/Domain/MEGAIntentDomain/Sources/MEGAIntentDomain/UseCase/IntentPersonUseCase.swift b/Modules/Domain/MEGAIntentDomain/Sources/MEGAIntentDomain/UseCase/IntentPersonUseCase.swift new file mode 100644 index 0000000000..f252a2bcdc --- /dev/null +++ b/Modules/Domain/MEGAIntentDomain/Sources/MEGAIntentDomain/UseCase/IntentPersonUseCase.swift @@ -0,0 +1,46 @@ +import Contacts +import Intents +import MEGADomain + +protocol IntentPersonUseCaseProtocol { + func personsInContacts(matching person: INPerson) -> [INPerson] +} + +public struct IntentPersonUseCase: IntentPersonUseCaseProtocol { + private let repository: any ContactsRepositoryProtocol + + public init(repository: some ContactsRepositoryProtocol) { + self.repository = repository + } + + public func personsInContacts(matching person: INPerson) -> [INPerson] { + let contacts = repository.fetchContacts() + let personDisplayName = person.displayName.lowercased() + + let initialFilter = contacts.filter { contact in + guard contact.emailAddresses.isNotEmpty else { return false } + + return personDisplayName.contains(contact.firstName) || personDisplayName.contains(contact.lastName) + } + + let secondFilter = initialFilter.filter { contact in + personDisplayName.contains(contact.firstName) && personDisplayName.contains(contact.lastName) + } + + let filteredContacts = secondFilter.isNotEmpty ? secondFilter : initialFilter + + return filteredContacts + .persons(withDisplayName: personDisplayName) + .removeDuplicatesWhileKeepingTheOriginalOrder() + } +} + +private extension CNContact { + var firstName: String { + givenName.lowercased() + } + + var lastName: String { + familyName.lowercased() + } +} diff --git a/Modules/Domain/MEGAIntentDomain/Tests/MEGAIntentDomainTests/IntentPersonUseCaseTests.swift b/Modules/Domain/MEGAIntentDomain/Tests/MEGAIntentDomainTests/IntentPersonUseCaseTests.swift new file mode 100644 index 0000000000..7c1857bffb --- /dev/null +++ b/Modules/Domain/MEGAIntentDomain/Tests/MEGAIntentDomainTests/IntentPersonUseCaseTests.swift @@ -0,0 +1,90 @@ +import Contacts +import Intents +import MEGADomain +import MEGAIntentDomain +import XCTest + +class IntentPersonUseCaseTests: XCTestCase { + func testPersonsInContacts_whenSecondFilterNotEmpty_shouldReturnSecondFilterResult() { + let mockContactsRepository = MockContactsRepository(contacts: [ + makeContact(givenName: "John", familyName: "Doe", emailAddresses: ["johndoe@example.com"]), + makeContact(givenName: "Jane", familyName: "Doe", emailAddresses: ["jane@example.com"]) + ]) + + let sut = IntentPersonUseCase(repository: mockContactsRepository) + + let person = makePerson() + + let result = sut.personsInContacts(matching: person) + + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result.first, person) + } + + func testPersonsInContacts_whenSecondFilterEmpty_shouldReturnFirstFilterResult() { + let mockContactsRepository = MockContactsRepository(contacts: [ + makeContact(givenName: "John", familyName: "Smith", emailAddresses: ["johnsmith@example.com"]), + makeContact(givenName: "Jane", familyName: "Doe", emailAddresses: ["janedoe@example.com"]) + ]) + + let sut = IntentPersonUseCase(repository: mockContactsRepository) + + let person = makePerson() + + let result = sut.personsInContacts(matching: person) + + XCTAssertEqual(result.count, 2) + } + + func testPersonsInContacts_whenBothFiltersEmpty_shouldReturnEmpty() { + let mockContactsRepository = MockContactsRepository(contacts: [ + makeContact(givenName: "Emily", familyName: "Smith", emailAddresses: ["emilysmith@example.com"]) + ]) + + let sut = IntentPersonUseCase(repository: mockContactsRepository) + + let person = makePerson() + + let result = sut.personsInContacts(matching: person) + + XCTAssertTrue(result.isEmpty) + } + + func makeContact(givenName: String, familyName: String, emailAddresses: [String]) -> CNContact { + let contact = CNMutableContact() + contact.givenName = givenName + contact.familyName = familyName + contact.emailAddresses = emailAddresses.map { CNLabeledValue(label: CNLabelHome, value: $0 as NSString) } + return contact + } + + func makePerson() -> INPerson { + INPerson( + personHandle: .init(value: "johndoe@example.com", type: .emailAddress), + nameComponents: nil, + displayName: "john doe", + image: nil, + contactIdentifier: nil, + customIdentifier: nil + ) + } +} + +private extension IntentPersonUseCaseTests { + struct MockContactsRepository: ContactsRepositoryProtocol { + static var newRepo: MockContactsRepository { + MockContactsRepository(contacts: []) + } + + var isAuthorizedToAccessPhoneContacts: Bool = true + let contactsToReturn: [CNContact] + + init(contacts: [CNContact]) { + self.contactsToReturn = contacts + } + + func fetchContacts() -> [CNContact] { + contactsToReturn + } + } +} diff --git a/Modules/Domain/MEGAPickerFileProviderDomain/Package.swift b/Modules/Domain/MEGAPickerFileProviderDomain/Package.swift index 33d95666b7..81725fa825 100644 --- a/Modules/Domain/MEGAPickerFileProviderDomain/Package.swift +++ b/Modules/Domain/MEGAPickerFileProviderDomain/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "MEGAPickerFileProviderDomain", platforms: [ @@ -19,11 +21,13 @@ let package = Package( targets: [ .target( name: "MEGAPickerFileProviderDomain", - dependencies: ["MEGADomain"] + dependencies: ["MEGADomain"], + swiftSettings: settings ), .testTarget( name: "MEGAPickerFileProviderDomainTests", - dependencies: ["MEGAPickerFileProviderDomain"] + dependencies: ["MEGAPickerFileProviderDomain"], + swiftSettings: settings ) ] ) diff --git a/Modules/Features/Search/Package.swift b/Modules/Features/Search/Package.swift index a8b68ecce8..40dbbcaa97 100644 --- a/Modules/Features/Search/Package.swift +++ b/Modules/Features/Search/Package.swift @@ -10,15 +10,34 @@ let package = Package( products: [ .library( name: "Search", - targets: ["Search"]), + targets: ["Search"] + ), + .library( + name: "SearchMock", + targets: ["SearchMock"] + ) + + ], + dependencies: [ + .package(path: "../../../UI/MEGASwiftUI") ], - dependencies: [], targets: [ .target( name: "Search", - dependencies: []), + dependencies: [ + "MEGASwiftUI" + ], + swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + ), + .target( + name: "SearchMock", + dependencies: ["Search"], + swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + ), .testTarget( name: "SearchTests", - dependencies: ["Search"]), + dependencies: ["Search", "SearchMock"], + swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + ) ] ) diff --git a/Modules/Features/Search/Sources/Search/Infrastructure/AuxilaryTypes.swift b/Modules/Features/Search/Sources/Search/Infrastructure/AuxilaryTypes.swift new file mode 100644 index 0000000000..3699f7c448 --- /dev/null +++ b/Modules/Features/Search/Sources/Search/Infrastructure/AuxilaryTypes.swift @@ -0,0 +1,17 @@ +import UIKit + +/// Used to represent different properties of results as images +/// ie: +/// label, isLiked, isDisputed, isOffline etc + +public struct Property { + // using data instead of String or (UI)Image makes the icon platform and UI framework agnostic + // (we can create UIKit.UIImage or SwiftUI.Image from this Data) but also support + // memory generated images for example for dynamically drawn images with dynamic properties + public let icon: Data +} + +/// represents a type of result, currently only node, in the future: a chat, a contact etc +public enum ResultType { + case node +} diff --git a/Modules/Features/Search/Sources/Search/Infrastructure/SearchBridge.swift b/Modules/Features/Search/Sources/Search/Infrastructure/SearchBridge.swift new file mode 100644 index 0000000000..844661e809 --- /dev/null +++ b/Modules/Features/Search/Sources/Search/Infrastructure/SearchBridge.swift @@ -0,0 +1,19 @@ +/// This class facilitates communication between the parent of the search feature and +/// the search view model. +/// Acts as an abstraction to not pollute view model interface with many closures and makes testing easier +public class SearchBridge { + public init( + selection: @escaping (SearchResult) -> Void, + context: @escaping (SearchResult) -> Void + ) { + self.selection = selection + self.context = context + } + + var selection: (SearchResult) -> Void + var context: (SearchResult) -> Void + + public var queryChanged: (String) -> Void = { _ in } + public var queryCleaned: () -> Void = { } + public var searchCancelled: () -> Void = { } +} diff --git a/Modules/Features/Search/Sources/Search/Infrastructure/SearchChipsEntity.swift b/Modules/Features/Search/Sources/Search/Infrastructure/SearchChipsEntity.swift new file mode 100644 index 0000000000..d17b76b2d5 --- /dev/null +++ b/Modules/Features/Search/Sources/Search/Infrastructure/SearchChipsEntity.swift @@ -0,0 +1,47 @@ +/// represents a chip filter item +/// api currently supports the list of types you can use to filter is: +/// +/// FILE_TYPE_PHOTO +/// FILE_TYPE_AUDIO +/// FILE_TYPE_VIDEO +/// FILE_TYPE_DOCUMENT +/// FILE_TYPE_PDF +/// FILE_TYPE_PRESENTATION +/// FILE_TYPE_ARCHIVE +/// FILE_TYPE_PROGRAM +/// FILE_TYPE_MISC +/// +/// https://code.developers.mega.co.nz/sdk/sdk/-/blame/develop/include/megaapi.h?page=16#L15824 + +public struct SearchChipEntity: Equatable { + + public let id: ChipId + public let title: String + public let icon: String? + + public init( + id: ChipId, + title: String, + icon: String? = nil + ) { + self.id = id + self.title = title + self.icon = icon + } +} + +public struct ChipId: Equatable { + let id: String +} + +extension ChipId: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + id = value + } +} + +extension ChipId: CustomStringConvertible { + public var description: String { + id + } +} diff --git a/Modules/Features/Search/Sources/Search/Infrastructure/SearchQueryEntity.swift b/Modules/Features/Search/Sources/Search/Infrastructure/SearchQueryEntity.swift new file mode 100644 index 0000000000..388d2a1391 --- /dev/null +++ b/Modules/Features/Search/Sources/Search/Infrastructure/SearchQueryEntity.swift @@ -0,0 +1,31 @@ +/// Represents a single search request +public struct SearchQueryEntity: Equatable { + public let query: String + public let sorting: SortOrderEntity + public let mode: SearchModeEntity + public let chips: [SearchChipEntity] + + public init(query: String, sorting: SortOrderEntity, mode: SearchModeEntity, chips: [SearchChipEntity]) { + self.query = query + self.sorting = sorting + self.mode = mode + self.chips = chips + } +} + +/// default value, iteration one of search project does not allow specifying sort order but that can come in next stages +public enum SortOrderEntity { + case automatic +} + +/// Specifies the context in which the search is happening +/// currently only home +/// next stages could be: +/// .cloudDrive(rootNode: Node) +/// .incomingShares +/// .chats +/// .outgoingShares +/// .offlineFiles +public enum SearchModeEntity { + case home +} diff --git a/Modules/Features/Search/Sources/Search/Infrastructure/SearchResult.swift b/Modules/Features/Search/Sources/Search/Infrastructure/SearchResult.swift new file mode 100644 index 0000000000..448e7ad8a7 --- /dev/null +++ b/Modules/Features/Search/Sources/Search/Infrastructure/SearchResult.swift @@ -0,0 +1,71 @@ +import Foundation + +/// Represents a single results of search action +/// still deciding which one to use +/// protocol or struct +public protocol SearchResultProtocol: Identifiable { + var id: ResultId { get } + var title: String { get } + var description: String { get } + var properties: [Property] { get } + func loadThumbnailImageData() async throws -> Data + var type: ResultType { get } +} + +public struct SearchResult: Identifiable { + public let id: ResultId + public let title: String + public let description: String + public let properties: [Property] + public let thumbnailImageData: () async -> Data + public let type: ResultType + + public init( + id: ResultId, + title: String, + description: String, + properties: [Property], + thumbnailImageData: @escaping () async -> Data, + type: ResultType + ) { + self.id = id + self.title = title + self.description = description + self.properties = properties + self.thumbnailImageData = thumbnailImageData + self.type = type + } +} + +public struct ResultId: Hashable { + let id: String +} + +extension ResultId: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + id = value + } +} + +extension ResultId: CustomStringConvertible { + public var description: String { + id + } +} + +extension SearchResult: Equatable { + public static func == (lhs: SearchResult, rhs: SearchResult) -> Bool { + lhs.id == rhs.id && + lhs.title == rhs.title && + lhs.description == rhs.description && + lhs.properties == rhs.properties && + lhs.type == rhs.type + } +} + +extension Property: Equatable { + public static func == (lhs: Property, rhs: Property) -> Bool { + lhs.icon == rhs.icon + } +} + diff --git a/Modules/Features/Search/Sources/Search/Infrastructure/SearchResultsEntity.swift b/Modules/Features/Search/Sources/Search/Infrastructure/SearchResultsEntity.swift new file mode 100644 index 0000000000..16cacb4441 --- /dev/null +++ b/Modules/Features/Search/Sources/Search/Infrastructure/SearchResultsEntity.swift @@ -0,0 +1,15 @@ +/// Represents a full response from search provider with given search query +/// contains a collection of results +/// and collection of chips (those are static but have potential be dynamically adjust depending on the context) +public struct SearchResultsEntity { + public let results: [SearchResult] + public let chips: [SearchChipEntity] + + public init( + results: [SearchResult], + chips: [SearchChipEntity] + ) { + self.results = results + self.chips = chips + } +} diff --git a/Modules/Features/Search/Sources/Search/Infrastructure/SearchResultsProviding.swift b/Modules/Features/Search/Sources/Search/Infrastructure/SearchResultsProviding.swift new file mode 100644 index 0000000000..e57b150408 --- /dev/null +++ b/Modules/Features/Search/Sources/Search/Infrastructure/SearchResultsProviding.swift @@ -0,0 +1,4 @@ +// Main interface used to execute searches +public protocol SearchResultsProviding { + func search(queryRequest: SearchQueryEntity) async throws -> SearchResultsEntity +} diff --git a/Modules/Features/Search/Sources/Search/NonProductTestSearchrResultsProvider.swift b/Modules/Features/Search/Sources/Search/NonProductTestSearchrResultsProvider.swift new file mode 100644 index 0000000000..8afc096c9d --- /dev/null +++ b/Modules/Features/Search/Sources/Search/NonProductTestSearchrResultsProvider.swift @@ -0,0 +1,30 @@ +import Foundation +import UIKit + +/// Development only implementation, will be moved to SearchMocks on next MR once +/// we have actual results provider using real SDK +public struct NonProductionTestResultsProvider: SearchResultsProviding { + public init() {} + + public func search(queryRequest: SearchQueryEntity) async throws -> SearchResultsEntity { + + let results: [SearchResult] = Array(0...queryRequest.query.count).map { + SearchResult( + id: ResultId(id: $0.description), + title: "Result \($0)", + description: "Description \($0)", + properties: [], + thumbnailImageData: { + // sample data to show any images + return UIImage(systemName: "signature")!.jpegData(compressionQuality: 100)! + }, + type: .node + ) + } + + return .init( + results: results, + chips: [] + ) + } +} diff --git a/Modules/Features/Search/Sources/Search/Search.swift b/Modules/Features/Search/Sources/Search/Search.swift deleted file mode 100644 index 35047ebcde..0000000000 --- a/Modules/Features/Search/Sources/Search/Search.swift +++ /dev/null @@ -1 +0,0 @@ -public struct Search {} diff --git a/Modules/Features/Search/Sources/Search/UI/SearchResultRowView.swift b/Modules/Features/Search/Sources/Search/UI/SearchResultRowView.swift new file mode 100644 index 0000000000..b77c7f638e --- /dev/null +++ b/Modules/Features/Search/Sources/Search/UI/SearchResultRowView.swift @@ -0,0 +1,74 @@ +import MEGASwiftUI +import SwiftUI + +struct SearchResultRowView: View { + @StateObject var viewModel: SearchResultRowViewModel + + public var body: some View { + HStack(spacing: 8) { + thumbnail + HStack { + titleAndDescription + Spacer() + more + } + } + .padding([.leading, .trailing]) + .frame(height: 65) + .contentShape(Rectangle()) + .onTapGesture { + viewModel.selectionAction() + } + .taskForiOS14 { + await viewModel.loadThumbnail() + } + } + + private var thumbnail: some View { + Image(uiImage: viewModel.thumbnailImage) + .resizable() + .scaledToFit() + .frame(width: 40, height: 40) + } + + private var titleAndDescription: some View { + VStack(alignment: .leading, spacing: .zero) { + Text(viewModel.title) + .font(.system(size: 15, weight: .medium)) + .foregroundColor(.primary) + Spacer() + Text(viewModel.subtitle) + .font(.system(size: 12, weight: .regular)) + .foregroundColor(.primary) + } + .frame(height: 40) + } + + private var more: some View { + Image("moreList") + .onTapGesture { + viewModel.contextAction() + } + } +} + +struct SearchResultRowView_Previews: PreviewProvider { + static var previews: some View { + let id = ResultId(id: "1") + + return SearchResultRowView( + viewModel: .init( + with: .init( + id: id, + title: "title_\(id)", + description: "subtitle_\(id)", + properties: [], + thumbnailImageData: { UIImage(systemName: "placeholder")?.pngData() ?? Data() }, + type: .node + ), + contextAction: { }, + selectionAction: { } + ) + ) + } +} diff --git a/Modules/Features/Search/Sources/Search/UI/SearchResultRowViewModel.swift b/Modules/Features/Search/Sources/Search/UI/SearchResultRowViewModel.swift new file mode 100644 index 0000000000..a751902095 --- /dev/null +++ b/Modules/Features/Search/Sources/Search/UI/SearchResultRowViewModel.swift @@ -0,0 +1,43 @@ +import SwiftUI + +final class SearchResultRowViewModel: ObservableObject, Identifiable, Equatable { + static func == (lhs: SearchResultRowViewModel, rhs: SearchResultRowViewModel) -> Bool { + rhs.result == lhs.result + } + + @Published var thumbnailImage: UIImage = UIImage() + + var title: String { + result.title + } + + var id: String { + result.id.description + } + + var subtitle: String { + result.description + } + + private let result: SearchResult + let contextAction: () -> Void + let selectionAction: () -> Void + + init( + with result: SearchResult, + contextAction: @escaping () -> Void, + selectionAction: @escaping () -> Void + ) { + self.result = result + self.contextAction = contextAction + self.selectionAction = selectionAction + } + + @MainActor + func loadThumbnail() async { + let data = await result.thumbnailImageData() + if let image = UIImage(data: data) { + thumbnailImage = image + } + } +} diff --git a/Modules/Features/Search/Sources/Search/UI/SearchResultsView.swift b/Modules/Features/Search/Sources/Search/UI/SearchResultsView.swift new file mode 100644 index 0000000000..9581ba7e61 --- /dev/null +++ b/Modules/Features/Search/Sources/Search/UI/SearchResultsView.swift @@ -0,0 +1,64 @@ +import SwiftUI + +public struct SearchResultsView: View { + public init(viewModel: @autoclosure @escaping () -> SearchResultsViewModel) { + _viewModel = StateObject(wrappedValue: viewModel()) + } + + @StateObject var viewModel: SearchResultsViewModel + + public var body: some View { + ScrollView { + LazyVStack { + ForEach(viewModel.listItems) { item in + SearchResultRowView(viewModel: item) + } + } + .overlay( + emptyView + ) + .frame( + maxWidth: .infinity, + maxHeight: .infinity, + alignment: .topLeading + ) + } + } + + @ViewBuilder + var emptyView: some View { + if viewModel.listItems.isEmpty { + VStack { + Spacer() + Text("No results") + Spacer() + } + } + } +} + +@available(iOS 15.0, *) +struct SearchResultsViewPreviews: PreviewProvider { + + struct Wrapper: View { + @State var text: String = "" + @StateObject var viewModel = SearchResultsViewModel( + resultsProvider: NonProductionTestResultsProvider(), + bridge: .init(selection: { _ in }, context: {_ in }) + ) + var body: some View { + SearchResultsView( + viewModel: viewModel + ) + .onChange(of: text, perform: { newValue in + viewModel.bridge.queryChanged(newValue) + }) + .searchable(text: $text) + } + } + static var previews: some View { + NavigationView { + Wrapper() + } + } +} diff --git a/Modules/Features/Search/Sources/Search/UI/SearchResultsViewModel.swift b/Modules/Features/Search/Sources/Search/UI/SearchResultsViewModel.swift new file mode 100644 index 0000000000..e4a650d6ea --- /dev/null +++ b/Modules/Features/Search/Sources/Search/UI/SearchResultsViewModel.swift @@ -0,0 +1,84 @@ +import SwiftUI + +public class SearchResultsViewModel: ObservableObject { + @Published var listItems: [SearchResultRowViewModel] = [] + + let bridge: SearchBridge + + private let resultsProvider: any SearchResultsProviding + + public init( + resultsProvider: any SearchResultsProviding, + bridge: SearchBridge + ) { + self.resultsProvider = resultsProvider + self.bridge = bridge + + self.bridge.queryChanged = { [weak self] in + self?.queryChanged(to: $0) + } + + self.bridge.queryCleaned = { [weak self] in + self?.queryCleaned() + } + + self.bridge.searchCancelled = { [weak self] in + self?.searchCancelled() + } + } + + func queryCleaned() { + listItems = [] + // show empty view here + } + + func searchCancelled() { + // cancel button on the searrch bar was tapped + listItems = [] + } + + func queryChanged(to query: String) { + + let queryEntity = SearchQueryEntity( + query: query, + sorting: .automatic, + mode: .home, + chips: [] + ) + + Task { @MainActor in + await performSearch(using: queryEntity) + } + } + + @MainActor + func performSearch(using query: SearchQueryEntity) async { + if query.query.isEmpty { + queryCleaned() + return + } + + var result: SearchResultsEntity? + do { + result = try await resultsProvider.search(queryRequest: query) + } catch { + // present error + } + + guard let result else { return } + + listItems = result.results.map { result in + SearchResultRowViewModel( + with: result, + contextAction: { [weak self] in + // present menucontext + self?.bridge.context(result) + }, + selectionAction: { [weak self] in + // executeSelect + self?.bridge.selection(result) + } + ) + } + } +} diff --git a/Modules/Features/Search/Sources/SearchMock/MockSearchResultsProviding.swift b/Modules/Features/Search/Sources/SearchMock/MockSearchResultsProviding.swift new file mode 100644 index 0000000000..2dbdbd1654 --- /dev/null +++ b/Modules/Features/Search/Sources/SearchMock/MockSearchResultsProviding.swift @@ -0,0 +1,52 @@ +import Search + +public class MockSearchResultsProviding: SearchResultsProviding { + + public var passedInQueries: [SearchQueryEntity] = [] + public var resultFactory: (_ query: SearchQueryEntity) async throws -> SearchResultsEntity + + public init() { + resultFactory = { _ in + .defaultTestResult + } + } + + public func search( + queryRequest: SearchQueryEntity + ) async throws -> SearchResultsEntity { + passedInQueries.append(queryRequest) + return try await resultFactory(queryRequest) + } +} + +extension SearchResultsEntity { + static var defaultTestResult: Self { + .init( + results: [.resultWith(id: "1")], + chips: [.chipWith(id: "2")] + ) + } +} + +extension SearchResult { + static func resultWith(id: ResultId) -> Self { + .init( + id: id, + title: "title_\(id)", + description: "subtitle_\(id)", + properties: [], + thumbnailImageData: { .init() }, + type: .node + ) + } +} + +extension SearchChipEntity { + static func chipWith(id: ChipId) -> Self { + .init( + id: id, + title: "chip_\(id)", + icon: nil + ) + } +} diff --git a/Modules/Features/Search/Tests/SearchTests/SearchResultsViewModelTests.swift b/Modules/Features/Search/Tests/SearchTests/SearchResultsViewModelTests.swift new file mode 100644 index 0000000000..19b213dcf4 --- /dev/null +++ b/Modules/Features/Search/Tests/SearchTests/SearchResultsViewModelTests.swift @@ -0,0 +1,131 @@ +@testable import Search +import SearchMock +import XCTest + +@MainActor +final class SearchResultsViewModelTests: XCTestCase { + class Harness { + let sut: SearchResultsViewModel + let resultsProvider: MockSearchResultsProviding + let bridge: SearchBridge + var selectedResults: [SearchResult] = [] + var contextTriggeredResults: [SearchResult] = [] + weak var testcase: XCTestCase? + + init(_ testcase: XCTestCase) { + self.testcase = testcase + resultsProvider = MockSearchResultsProviding() + var selection: (SearchResult) -> Void = { _ in } + var context: (SearchResult) -> Void = { _ in } + bridge = SearchBridge( + selection: { selection($0) }, + context: { context($0) } + ) + sut = SearchResultsViewModel( + resultsProvider: resultsProvider, + bridge: bridge + ) + selection = { + self.selectedResults.append($0) + } + context = { + self.contextTriggeredResults.append($0) + } + } + + func withResultsPrepared() -> Self { + let results = SearchResultsEntity( + results: [ + .sampleResult + ], + chips: [] + ) + + resultsProvider.resultFactory = { _ in + return results + } + + return self + } + + func searchAndWaitForResults(query: String) async { + sut.queryChanged(to: query) + await testcase?.wait(until: { + !self.sut.listItems.isEmpty + }) + } + } + + func testListItems_onStart_hasNoItems() { + let harness = Harness(self) + XCTAssertEqual(harness.sut.listItems, []) + } + + func testChangingQuery_asksResultsProviderToPerformSearch() async { + let harness = Harness(self).withResultsPrepared() + harness.sut.queryChanged(to: "query") + await wait(until: { + harness.resultsProvider.passedInQueries == [.query("query")] + }) + } + + func testListItems_onQueryChanged_returnsResultsFromResultsProvider() async { + let harness = Harness(self).withResultsPrepared() + harness.sut.queryChanged(to: "query") + await wait(until: { + harness.sut.listItems == [.init(with: .sampleResult, contextAction: {}, selectionAction: {})] + }) + } + + func testListItems_onQueryCleaned_clearsAnyResults() async { + let harness = Harness(self).withResultsPrepared() + harness.sut.queryChanged(to: "query") + await wait(until: { + !harness.sut.listItems.isEmpty + }) + harness.sut.queryCleaned() + await wait(until: { + harness.sut.listItems.isEmpty + }) + } + + func testOnSelectionAction_passesSelectedResultViaBridge() async throws { + let harness = Harness(self).withResultsPrepared() + await harness.searchAndWaitForResults(query: "query") + let item = try XCTUnwrap(harness.sut.listItems.first) + item.selectionAction() + XCTAssertEqual(harness.selectedResults, [.sampleResult]) + } + + func testOnContextAction_passesSelectedResultViaBridge() async throws { + let harness = Harness(self).withResultsPrepared() + await harness.searchAndWaitForResults(query: "query") + let item = try XCTUnwrap(harness.sut.listItems.first) + item.contextAction() + XCTAssertEqual(harness.contextTriggeredResults, [.sampleResult]) + } +} + +fileprivate extension SearchQueryEntity { + static func query(_ string: String) -> Self { + .init( + query: string, + sorting: .automatic, + mode: .home, + chips: [] + ) + } +} + +fileprivate extension SearchResult { + static var sampleResult: Self { + .init( + id: "0", + title: "title", + description: "desc", + properties: [], + thumbnailImageData: { Data() }, + type: .node + ) + } +} diff --git a/Modules/Features/Search/Tests/SearchTests/SearchTests.swift b/Modules/Features/Search/Tests/SearchTests/SearchTests.swift deleted file mode 100644 index 047afbc9c0..0000000000 --- a/Modules/Features/Search/Tests/SearchTests/SearchTests.swift +++ /dev/null @@ -1,4 +0,0 @@ -import XCTest -@testable import Search - -final class SearchTests: XCTestCase {} diff --git a/Modules/Features/Search/Tests/SearchTests/XCTestHelpers.swift b/Modules/Features/Search/Tests/SearchTests/XCTestHelpers.swift new file mode 100644 index 0000000000..98fbb3002f --- /dev/null +++ b/Modules/Features/Search/Tests/SearchTests/XCTestHelpers.swift @@ -0,0 +1,11 @@ +import XCTest + +extension XCTestCase { + func wait(until: @escaping () -> Bool) async { + let predicate = NSPredicate { _, _ in + until() + } + let expectation = expectation(for: predicate, evaluatedWith: nil) + await fulfillment(of: [expectation]) + } +} diff --git a/Modules/Infrastracture/MEGAFoundation/Package.swift b/Modules/Infrastracture/MEGAFoundation/Package.swift index 2c25e6104b..544165c373 100644 --- a/Modules/Infrastracture/MEGAFoundation/Package.swift +++ b/Modules/Infrastracture/MEGAFoundation/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "MEGAFoundation", platforms: [ @@ -18,10 +20,10 @@ let package = Package( .target( name: "MEGAFoundation", dependencies: [], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")]), + swiftSettings: settings), .testTarget( name: "MEGAFoundationTests", dependencies: ["MEGAFoundation"], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")]) + swiftSettings: settings) ] ) diff --git a/Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/AVAudioSession/AVAudioSession+Additions.swift b/Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/AVAudioSession/AVAudioSession+Additions.swift new file mode 100644 index 0000000000..9f0a0ee326 --- /dev/null +++ b/Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/AVAudioSession/AVAudioSession+Additions.swift @@ -0,0 +1,17 @@ +import AVFoundation + +@objc public extension AVAudioSession { + var isBluetoothAudioRouteAvailable: Bool { + guard let availableInputs = self.availableInputs else { + return false + } + + let bluetoothPorts: Set = [.bluetoothA2DP, .bluetoothLE, .bluetoothHFP] + + return availableInputs.contains { bluetoothPorts.contains($0.portType) } + } + + func isOutputEqualToPortType(_ portType: AVAudioSession.Port) -> Bool { + currentRoute.outputs.first?.portType == portType + } +} diff --git a/Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/CNContact/CNConact+Additions.swift b/Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/CNContact/CNContact+Additions.swift similarity index 68% rename from Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/CNContact/CNConact+Additions.swift rename to Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/CNContact/CNContact+Additions.swift index 72d79fdcc6..0dd085d001 100644 --- a/Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/CNContact/CNConact+Additions.swift +++ b/Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/CNContact/CNContact+Additions.swift @@ -1,7 +1,7 @@ import Contacts import Intents -public extension [CNContact] { +public extension Array where Element: CNContact { func persons(withDisplayName displayName: String) -> [INPerson] { let persons = self.flatMap { contact -> [INPerson] in let persons = contact.emailAddresses.compactMap { @@ -17,4 +17,14 @@ public extension [CNContact] { } return persons } + + func extractEmails() -> [String] { + self + .compactMap { $0.emailAddresses.first?.value as String? } + } + + func extractPhoneNumbers() -> [String] { + self + .compactMap { $0.phoneNumbers.first?.value.stringValue } + } } diff --git a/Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/Extensions/DispatchQueue+Additions.swift b/Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/DispatchQueue/DispatchQueue+Additions.swift similarity index 100% rename from Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/Extensions/DispatchQueue+Additions.swift rename to Modules/Infrastracture/MEGAFoundation/Sources/MEGAFoundation/DispatchQueue/DispatchQueue+Additions.swift diff --git a/Modules/Infrastracture/MEGAFoundation/Tests/MEGAFoundationTests/CNContact/CNContactTests.swift b/Modules/Infrastracture/MEGAFoundation/Tests/MEGAFoundationTests/CNContact/CNContactTests.swift new file mode 100644 index 0000000000..d27738dd7d --- /dev/null +++ b/Modules/Infrastracture/MEGAFoundation/Tests/MEGAFoundationTests/CNContact/CNContactTests.swift @@ -0,0 +1,69 @@ +import Contacts +import XCTest + +final class CNContactTests: XCTestCase { + func testExtractPhoneNumbers_whenExtractingPhoneNumbers_shouldReturnCorrectPhoneNumbers() { + let phoneNumberFirst = CNPhoneNumber(stringValue: "123-456-7890") + let phoneNumberSecond = CNPhoneNumber(stringValue: "098-765-4321") + + let firstContact = CNMutableContact() + firstContact.phoneNumbers = [CNLabeledValue(label: CNLabelWork, value: phoneNumberFirst)] + + let secondContact = CNMutableContact() + secondContact.phoneNumbers = [CNLabeledValue(label: CNLabelHome, value: phoneNumberSecond)] + + let sut = [firstContact, secondContact] + + let result = sut.extractPhoneNumbers() + + XCTAssertEqual(result, ["123-456-7890", "098-765-4321"]) + } + + func testExtractEmails_whenExtractingEmails_shouldReturnCorrectEmails() { + let emailFirst = CNLabeledValue(label: CNLabelHome, value: "first@example.com" as NSString) + let emailSecond = CNLabeledValue(label: CNLabelWork, value: "second@example.com" as NSString) + + let firstContact = CNMutableContact() + firstContact.emailAddresses = [emailFirst] + + let secondContact = CNMutableContact() + secondContact.emailAddresses = [emailSecond] + + let sut = [firstContact, secondContact] + + let result = sut.extractEmails() + + XCTAssertEqual(result, ["first@example.com", "second@example.com"]) + } + + func testExtractPhoneNumbers_whenMultiplePhoneNumbers_shouldReturnFirstPhoneNumber() { + let phoneNumberFirst = CNPhoneNumber(stringValue: "123-456-7890") + let phoneNumberSecond = CNPhoneNumber(stringValue: "098-765-4321") + + let firstContact = CNMutableContact() + firstContact.phoneNumbers = [ + CNLabeledValue(label: CNLabelWork, value: phoneNumberFirst), + CNLabeledValue(label: CNLabelHome, value: phoneNumberSecond) + ] + + let sut = [firstContact] + + let result = sut.extractPhoneNumbers() + + XCTAssertEqual(result, ["123-456-7890"]) + } + + func testExtractEmails_whenMultipleEmails_shouldReturnFirstEmail() { + let emailFirst = CNLabeledValue(label: CNLabelHome, value: "first@example.com" as NSString) + let emailSecond = CNLabeledValue(label: CNLabelWork, value: "second@example.com" as NSString) + + let firstContact = CNMutableContact() + firstContact.emailAddresses = [emailFirst, emailSecond] + + let sut = [firstContact] + + let result = sut.extractEmails() + + XCTAssertEqual(result, ["first@example.com"]) + } +} diff --git a/Modules/Infrastracture/MEGAFoundation/Tests/MEGAFoundationTests/Time/TimeIntervalTests.swift b/Modules/Infrastracture/MEGAFoundation/Tests/MEGAFoundationTests/Time/TimeIntervalTests.swift index 1d205b9b49..01129152b2 100644 --- a/Modules/Infrastracture/MEGAFoundation/Tests/MEGAFoundationTests/Time/TimeIntervalTests.swift +++ b/Modules/Infrastracture/MEGAFoundation/Tests/MEGAFoundationTests/Time/TimeIntervalTests.swift @@ -1,4 +1,3 @@ - import XCTest final class TimeIntervalTests: XCTestCase { diff --git a/Modules/Infrastracture/MEGAPermissions/Package.swift b/Modules/Infrastracture/MEGAPermissions/Package.swift index 051a3e99bc..cb5d57144c 100644 --- a/Modules/Infrastracture/MEGAPermissions/Package.swift +++ b/Modules/Infrastracture/MEGAPermissions/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "MEGAPermissions", platforms: [ @@ -23,17 +25,17 @@ let package = Package( .target( name: "MEGAPermissions", dependencies: [], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + swiftSettings: settings ), .target( name: "MEGAPermissionsMock", dependencies: ["MEGAPermissions"], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + swiftSettings: settings ), .testTarget( name: "MEGAPermissionsTests", dependencies: ["MEGAPermissions", "MEGAPermissionsMock"], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + swiftSettings: settings ) ] ) diff --git a/Modules/Infrastracture/MEGAPermissions/Sources/MEGAPermissionsMock/MockDevicePermissionsHandler.swift b/Modules/Infrastracture/MEGAPermissions/Sources/MEGAPermissionsMock/MockDevicePermissionsHandler.swift index 538ef01553..c46bf6b55d 100644 --- a/Modules/Infrastracture/MEGAPermissions/Sources/MEGAPermissionsMock/MockDevicePermissionsHandler.swift +++ b/Modules/Infrastracture/MEGAPermissions/Sources/MEGAPermissionsMock/MockDevicePermissionsHandler.swift @@ -5,26 +5,29 @@ import Photos import UserNotifications public class MockDevicePermissionHandler: DevicePermissionsHandling { - public init() { - - } + + private var requestPhotoLibraryAccessPermissionsGranted: Bool = false + + public init() { } public convenience init( photoAuthorization: PHAuthorizationStatus, audioAuthorized: Bool, - videoAuthorized: Bool + videoAuthorized: Bool, + requestPhotoLibraryAccessPermissionsGranted: Bool = false ) { self.init() photoLibraryAuthorizationStatus = photoAuthorization requestMediaPermissionValuesToReturn[.audio] = audioAuthorized requestMediaPermissionValuesToReturn[.video] = videoAuthorized + self.requestPhotoLibraryAccessPermissionsGranted = requestPhotoLibraryAccessPermissionsGranted } public func notificationPermissionStatus() async -> UNAuthorizationStatus { .denied } - public func requestPhotoLibraryAccessPermissions() async -> Bool { false } + public func requestPhotoLibraryAccessPermissions() async -> Bool { requestPhotoLibraryAccessPermissionsGranted } public var requestPermissionsMediaTypes: [AVMediaType] = [] public var requestMediaPermissionValuesToReturn: [AVMediaType: Bool] = [:] diff --git a/Modules/Infrastracture/MEGASwift/Package.swift b/Modules/Infrastracture/MEGASwift/Package.swift index 14c038baf6..ed118b4f92 100644 --- a/Modules/Infrastracture/MEGASwift/Package.swift +++ b/Modules/Infrastracture/MEGASwift/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "MEGASwift", platforms: [ @@ -18,10 +20,10 @@ let package = Package( .target( name: "MEGASwift", dependencies: [], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")]), + swiftSettings: settings), .testTarget( name: "MEGASwiftTests", dependencies: ["MEGASwift"], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")]) + swiftSettings: settings) ] ) diff --git a/Modules/Infrastracture/MEGASwift/Sources/MEGASwift/AsyncValues.swift b/Modules/Infrastracture/MEGASwift/Sources/MEGASwift/AsyncValues.swift index f130bd8763..30665863ee 100644 --- a/Modules/Infrastracture/MEGASwift/Sources/MEGASwift/AsyncValues.swift +++ b/Modules/Infrastracture/MEGASwift/Sources/MEGASwift/AsyncValues.swift @@ -1,4 +1,3 @@ - public func withAsyncThrowingValue(in operation: (@escaping (Result) -> Void) -> Void) async throws -> T { return try await withCheckedThrowingContinuation { continuation in guard Task.isCancelled == false else { diff --git a/Modules/Infrastracture/MEGASwift/Sources/MEGASwift/Result+Additions.swift b/Modules/Infrastracture/MEGASwift/Sources/MEGASwift/Result+Additions.swift index a9ec3af12d..8dbfc7c8fa 100644 --- a/Modules/Infrastracture/MEGASwift/Sources/MEGASwift/Result+Additions.swift +++ b/Modules/Infrastracture/MEGASwift/Sources/MEGASwift/Result+Additions.swift @@ -1,4 +1,3 @@ - public extension Result where Success == Void { static var success: Result { return .success(()) diff --git a/Modules/Infrastracture/MEGATest/Package.swift b/Modules/Infrastracture/MEGATest/Package.swift index 07914b8b4d..81691624eb 100644 --- a/Modules/Infrastracture/MEGATest/Package.swift +++ b/Modules/Infrastracture/MEGATest/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "MEGATest", platforms: [ @@ -16,7 +18,7 @@ let package = Package( .target( name: "MEGATest", dependencies: [], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + swiftSettings: settings ) ] ) diff --git a/Modules/Localization/MEGAL10n/.gitignore b/Modules/Localization/MEGAL10n/.gitignore new file mode 100644 index 0000000000..3b29812086 --- /dev/null +++ b/Modules/Localization/MEGAL10n/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Modules/Localization/MEGAL10n/.swiftpm/xcode/xcshareddata/xcschemes/MEGALocalization.xcscheme b/Modules/Localization/MEGAL10n/.swiftpm/xcode/xcshareddata/xcschemes/MEGALocalization.xcscheme new file mode 100644 index 0000000000..b9e27954c6 --- /dev/null +++ b/Modules/Localization/MEGAL10n/.swiftpm/xcode/xcshareddata/xcschemes/MEGALocalization.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Localization/MEGAL10n/Package.swift b/Modules/Localization/MEGAL10n/Package.swift new file mode 100644 index 0000000000..01cda4dc8a --- /dev/null +++ b/Modules/Localization/MEGAL10n/Package.swift @@ -0,0 +1,44 @@ +// swift-tools-version: 5.8 +import PackageDescription + +let package = Package( + name: "MEGAL10n", + defaultLocalization: "en", + platforms: [ + .macOS(.v10_15), .iOS(.v14) + ], + products: [ + .library( + name: "MEGAL10n", + targets: ["MEGAL10n"] + ), + .library( + name: "MEGAL10nObjc", + targets: ["MEGAL10nObjc"] + ) + ], + dependencies: [ + .package(path: "../../BuildTools/MEGAPlugins") + ], + targets: [ + .target( + name: "MEGAL10n", + exclude: [ + "Localization.h", + "Localization.m" + ], + plugins: [ + .plugin(name: "SwiftGen", package: "MEGAPlugins"), + ] + ), + .target( + name: "MEGAL10nObjc", + path: "Sources/MEGAL10n", + exclude: [ + "Strings+Generated.swift", + "Localization.swift" + ], + publicHeadersPath: "." + ) + ] +) diff --git a/Modules/Localization/MEGAL10n/README.md b/Modules/Localization/MEGAL10n/README.md new file mode 100644 index 0000000000..f925969d44 --- /dev/null +++ b/Modules/Localization/MEGAL10n/README.md @@ -0,0 +1,3 @@ +# MEGAL10n + +MEGAL10n contains translations used in MEGA iOS project. diff --git a/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.h b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.h new file mode 100644 index 0000000000..0fd0372677 --- /dev/null +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.h @@ -0,0 +1,13 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +#define LocalizedString(key, developerComment) [Localization localizedValueForKey:key comment:developerComment] + +@interface Localization: NSObject + ++ (NSString *)localizedValueForKey:(NSString *)key comment:(NSString *)comment; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.m b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.m new file mode 100644 index 0000000000..549278cca3 --- /dev/null +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.m @@ -0,0 +1,9 @@ +#import "Localization.h" + +@implementation Localization + ++ (NSString *)localizedValueForKey:(NSString *)key comment:(NSString *)comment { + return [SWIFTPM_MODULE_BUNDLE localizedStringForKey:key value:key table:@"Localizable"]; +} + +@end diff --git a/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.swift b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.swift new file mode 100644 index 0000000000..026a1e8fa5 --- /dev/null +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Localization.swift @@ -0,0 +1,7 @@ +import Foundation + +public extension Strings { + static func localized(_ key: String, comment: String) -> String { + Bundle.module.localizedString(forKey: key, value: key, table: "Localizable") + } +} diff --git a/iMEGA/Languages/Base.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/Base.lproj/Localizable.strings similarity index 98% rename from iMEGA/Languages/Base.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/Base.lproj/Localizable.strings index b98eeaec09..5b821f27e1 100644 --- a/iMEGA/Languages/Base.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/Base.lproj/Localizable.strings @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="You have previously subscribed to a Pro plan with Google Play or AppGallery. Please manually cancel your subscription with them inside Google Play or the Huawei AppGallery on your device and then retry."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Do you want to cancel your current subscription and continue with the purchase?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Not available with your current plan"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Active subscription"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Update"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="The file will be updated with a new version."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Apply to all %@ duplicates"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="You’re the only one here"; /* Meetings end call dialog description */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Blocked"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Unknown device"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Show in Cloud drive"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="End recurrence"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Join"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wait for host to let you in"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wait for host to let you in"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Wait for host to start the meeting"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="Host didn’t let you in"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="OK, got it"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ is waiting to join the call"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Deny"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Admit"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"="%@ participants are waiting to join the call"; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="See waiting room"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Admit all"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Deny %@ entry?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Deny entry"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Cancel"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ is waiting to join “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"="%@ participants are waiting to join “%@”"; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Open in MEGA"; diff --git a/iMEGA/Languages/Base.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/Base.lproj/Localizable.stringsdict similarity index 99% rename from iMEGA/Languages/Base.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/Base.lproj/Localizable.stringsdict index dd3e871ee8..638d2bdfc3 100644 --- a/iMEGA/Languages/Base.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/Base.lproj/Localizable.stringsdict @@ -2706,5 +2706,37 @@ %d files saved to Cloud drive + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Download started + other + %d downloads started + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/iMEGA/Languages/ar.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ar.lproj/Localizable.strings similarity index 98% rename from iMEGA/Languages/ar.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ar.lproj/Localizable.strings index 74f29ca337..d33aef4ac1 100644 --- a/iMEGA/Languages/ar.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ar.lproj/Localizable.strings @@ -1057,7 +1057,7 @@ /* Text listed that includes the amount of storage that a user gets with a certain package. For example: '2 TB Storage'. */ "account.storageQuota"="%@ مساحة تخزين"; /* Text listed that includes the amount of storage that a free user gets: '20GB+ Storage' */ -"account.storage.freePlan"="[B]20 غيغابايت+[/B] من مساحة تخزين"; +"account.storage.freePlan"="[B]20+ غيغابايت[/B] من مساحة تخزين"; /* Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. */ "Transfer Quota"="تراسل المعطيات"; /* Text listed that includes the amount of transfer quota a user gets per month with a certain package. For example: '8 TB Transfer'. */ @@ -2047,7 +2047,7 @@ /* Text used as a section title or similar */ "Enter Email"="أدخل البريد الإلكتروني"; /* Text to send as SMS message to user contacts inviting them to MEGA */ -"contact.invite.message"="You have a MEGA Chat request waiting. Register an account on MEGA and get 20 GB free lifetime storage."; +"contact.invite.message"="لديك طلب لمحادثة ميغا MEGA الآمنة في الانتظار. قم بتسجيل حساب على ميغا MEGA واحصل على 20 غيغابايت من مساحة التخزين المجانية مدى الحياة."; /* Destination folder name of chat files */ "My chat files"="ملفات المحادثة الخاصة بي"; /* Title of the section about the plan in the storage tab in My Account Section */ @@ -2683,7 +2683,7 @@ /* Guest user join Mega: paragraph 1 description */ "meetings.joinMega.paragraph1.description"="انضم إلى أكبر منصة تخزين وتعاون سحابية آمنة في العالم."; /* Guest user join Mega: paragraph 2 title */ -"meetings.joinMega.paragraph2.title"="احصل على 20 جيجابايت مجاناً"; +"meetings.joinMega.paragraph2.title"="احصل على 20 غيغابايت مجاناً"; /* Guest user join Mega: paragraph 2 description */ "meetings.joinMega.paragraph2.description"="اشترك الآن واستمتع بميزات التعاون المتقدمة مجانًا."; /* Message shown when the user privilege is changed to moderator */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="لقد سبق لك الاشتراك في باقة برو Pro باستخدام غوغل بلاي Google Play أو اب غاليري AppGallery. يرجى إلغاء اشتراكك يدويًا داخل غوغل بلاي Google Play أو هواوي اب غاليري Huawei AppGallery على جهازك ثم إعادة المحاولة."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="هل تريد إلغاء اشتراكك الحالي ومتابعة الشراء؟"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Not available with your current plan"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="اشتراك مفعل"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="تحديث"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="سيتم استبدال الملف بالنسخة الجديدة."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="طبق على كل الـ %@ المكررين"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="انت الوحيد هنا"; /* Meetings end call dialog description */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="محظور"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Unknown device"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="عرض في السواقة السحابية"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="إنهاء التكرار"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="انضمام"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="انتظر حتى يسمح لك المضيف بالدخول"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="انتظر حتى يسمح لك المضيف بالدخول"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="انتظر المضيف لبدء الاجتماع"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="المضيف لم يسمح لك بالدخول"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="حسنًا، فهمت ذلك"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ is waiting to join the call"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="رفض"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="اسمح"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="See waiting room"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="اسمح للكل"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Deny %@ entry?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Deny entry"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="إلغاء"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ is waiting to join “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Open in MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/ar.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ar.lproj/Localizable.stringsdict similarity index 99% rename from iMEGA/Languages/ar.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ar.lproj/Localizable.stringsdict index e3054b4456..3f2b0b87cc 100644 --- a/iMEGA/Languages/ar.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ar.lproj/Localizable.stringsdict @@ -4010,5 +4010,37 @@ %d files saved to Cloud drive + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Download started + other + %d downloads started + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/iMEGA/Languages/de.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/de.lproj/Localizable.strings similarity index 97% rename from iMEGA/Languages/de.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/de.lproj/Localizable.strings index 6d722ba0dc..4081ad499b 100644 --- a/iMEGA/Languages/de.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/de.lproj/Localizable.strings @@ -1207,7 +1207,7 @@ /* Title of a warning recommending upgrade to Pro */ "Upgrade to Pro"="Auf Pro umsteigen"; /* A description of MEGA features available only with a Pro plan. */ -"Access Pro only features like setting password protection and expiry dates for public files."="Access Pro only features like setting password protection and expiry dates for public links."; +"Access Pro only features like setting password protection and expiry dates for public files."="Zugriff auf Pro-spezifische Features wie Passwortschutz und Ablaufdatum für öffentliche Links."; /* Alert title shown when the DEBUG mode is enabled */ "enableDebugMode_title"="Debug-Modus aktivieren"; /* Alert message shown when the DEBUG mode is enabled */ @@ -1805,7 +1805,7 @@ /* Message shown in a chat room for a one on one call */ "Tap to return to call"="Zum Gespräch zurückkehren"; /* A button label which allows the users save images/videos in the Photos app. */ -"Save to Photos"="In Photos speichern"; +"Save to Photos"="In Fotos speichern"; /* Text shown when starting the process to save a photo or video to Photos app */ "Saving to Photos…"="Speichere in Fotos…"; /* Text shown for switching from preview view to all media view. */ @@ -1813,7 +1813,7 @@ /* Label for the status of a transfer when is being Downloading - (String as short as possible. */ "Downloading %@"="Downloade %@"; /* Text shown when a photo or video is saved to Photos app */ -"Saved to Photos"="In Photos abgespeichert"; +"Saved to Photos"="In Fotos gespeichert"; /* Text shown when an error occurs when trying to save a photo or video to Photos app */ "Could not save Item"="Speicherung fehlgeschlagen"; /* Label in a cell where you can enable the 'Encrypted Key Rotation' */ @@ -1849,7 +1849,7 @@ /* Message show when a user cannot activate the video in a group call because the max number of videos has been reached */ "Error. No more video are allowed in this group call."="Die maximale Anzahl von Video-Teilnehmern ist erreicht, bitte beschränken Sie sich auf Audio."; /* Error shown when trying to start a call in a group with more peers than allowed */ -"Unable to start a call because the participants limit was exceeded."="Unable to start the call due to the participant limit having been exceeded."; +"Unable to start a call because the participants limit was exceeded."="Die Konferenz kann nicht gestartet werden, da das Teilnehmerlimit überschritten wurde."; /* Label shown while joining a public chat */ "Joining..."="Beitreten…"; /* Label shown while leaving a public chat */ @@ -1937,7 +1937,7 @@ /* 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"="Diese Aktion ist nicht möglich, da Sie dadurch Ihr aktuelles Speicherlimit überschreiten würden"; /* uploads over storage quota warning dialog title */ -"Your upload(s) cannot proceed because your account is full"="Your upload cannot proceed because your Cloud storage is full"; +"Your upload(s) cannot proceed because your account is full"="Ihr Upload kann nicht fortgesetzt werden, da Ihr Cloudspeicher voll ist"; /* 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."="Erlauben Sie Ihren Kontakten, den Zeitpunkt zu sehen, an dem Sie zuletzt online waren."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ @@ -2047,7 +2047,7 @@ /* Text used as a section title or similar */ "Enter Email"="E-Mail-Adresse eingeben"; /* Text to send as SMS message to user contacts inviting them to MEGA */ -"contact.invite.message"="You have a MEGA Chat request waiting. Register an account on MEGA and get 20 GB free lifetime storage."; +"contact.invite.message"="Sie haben eine Einladung zu MEGA Chat erhalten. Registrieren Sie einen MEGA-Account und erhalten Sie 20 GB kostenlosen Speicherplatz auf Lebenszeit."; /* Destination folder name of chat files */ "My chat files"="Chatdateien"; /* Title of the section about the plan in the storage tab in My Account Section */ @@ -2205,7 +2205,7 @@ /* tool bar title used in transfer widget, allow user to clear the selected items in the list */ "Clear Selected"="Auswahl aufheben"; /* tool bar title used in transfer widget, allow user to resume all transfers in the list */ -"Pause All"="Alle pausieren"; +"Pause All"="Alle anhalten"; /* tool bar title used in transfer widget, allow user to Pause all transfers in the list */ "Resume All"="Alle fortsetzen"; /* tool bar title used in transfer widget, allow user to clear all items in the list */ @@ -2585,11 +2585,11 @@ /* Over Disk Quota paywall not final detail message. */ "dialog.storage.paywall.notFinal.detail"="Es ist %@ her, dass wir Sie am %@ unter %@ angeschrieben haben. Leider haben Sie in dieser Zeit keine Maßnahmen ergriffen. Sie belegen immer noch %@ Speicherplatz, und dies liegt über Ihrem kostenlosen Limit. Weitere Informationen zu Ihren Möglichkeiten finden Sie in den E-Mails, die wir Ihnen gesendet haben."; /* Over Disk Quota paywall final detail message. */ -"dialog.storage.paywall.final.detail"="Your data will be deleted tomorrow if your account has not been upgraded."; +"dialog.storage.paywall.final.detail"="Ihre Daten werden morgen gelöscht, falls Sie bis dahin kein Upgrade Ihres Accounts durchgeführt haben."; /* Over Disk Quota paywall, not final warning label. */ -"dialog.storage.paywall.notFinal.warning"="Upgrade within %@ to avoid your data getting deleted."; +"dialog.storage.paywall.notFinal.warning"="Sie haben noch %@, um ein Upgrade durchzuführen und damit zu vermeiden, dass Ihre Daten gelöscht werden."; /* Over Disk Quota paywall, final warning label. */ -"dialog.storage.paywall.final.warning"="Upgrade today if you wish to keep your data."; +"dialog.storage.paywall.final.warning"="Führen Sie noch heute ein Upgrade durch, wenn Sie Ihre Daten behalten möchten."; /* Button title to see the available bonus */ "general.button.getBonus"="Bonus erhalten"; /* Backup setup warning title */ @@ -2599,7 +2599,7 @@ /* Change the default backup location warning title. %@ is a folder */ "dialog.backup.folder.location.warning.title"="„%@“ verschieben"; /* Change the default backup location warning message */ -"dialog.root.backup.folder.location.warning.message"="You are changing a default backup folder location. This may affect your ability to find your backup folder. Please remember where it is located so that you can find it in the future."; +"dialog.root.backup.folder.location.warning.message"="Sie sind dabei, den Speicherort eines Standard-Backup-Ordners zu ändern. Dies kann es Ihnen erschweren, Ihren Backup-Ordner zu finden. Bitte merken Sie sich den Speicherort, damit Sie den Ordner in Zukunft wiederfinden."; /* Change the default backup location warning message */ "dialog.backup.folder.location.warning.message"="Durch Verschieben dieses Ordners wird das Backup-Ziel geändert. Das Backup wird zur Sicherheit deaktiviert. Möchten Sie fortfahren? Backups können in der MEGA-Desktop-App erneut aktiviert werden."; /* Delete the default backup folder warning title. %@ is a folder */ @@ -2643,7 +2643,7 @@ /* Shown when an invalid/inexisting/not-available-anymore meeting link is opened. */ "meetings.alert.end"="Meeting beendet"; /* Meeting ended Alert button -- View Meeting Chat history. */ -"meetings.alert.meetingchat"="View chat history"; +"meetings.alert.meetingchat"="Chatverlauf ansehen"; /* Shown description when an invalid/inexisting/not-available-anymore meeting link is opened. */ "meetings.alert.end.description"="Das Meeting, dem Sie beitreten möchten, ist bereits beendet. Sie können den Chatverlauf des Meetings noch einsehen."; /* Contacts selection screen header: Invite contacts to meetings */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="Sie haben ein aktives Pro-Abonnement mit Google Play oder AppGallery. Bitte stornieren Sie dieses manuell in der Google-Play- oder der AppGallery-App und versuchen Sie es dann erneut."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Laufendes Abonnement stornieren und Kaufvorgang fortsetzen?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Nicht verfügbar mit Ihrem aktuellen Paket"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Aktives Abonnement"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Update"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="Die Datei wird mit einer neuen Version aktualisiert."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Auf alle %@ Duplikate anwenden"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="Sie sind der einzige Teilnehmer"; /* Meetings end call dialog description */ @@ -3153,9 +3153,9 @@ /* Start conversation: Schedule meeting context menu */ "meetings.startConversation.contextMenu.scheduleMeeting"="Meeting planen"; /* Error message when trying to login and the account is blocked due to copyright violation */ -"account.suspension.message.copyright"="Your MEGA account has been suspended due to repeated allegations of copyright infringements. This means you cannot access your account or data within it.\n\nCheck your email for more information on how to file a counter-notice."; +"account.suspension.message.copyright"="Ihr MEGA-Account wurde aufgrund von wiederholten Vorwürfen von Urheberrechtsverstößen gesperrt. Dies bedeutet, dass Sie nicht auf Ihren Account oder die darin enthaltenen Daten zugreifen können.\n\nIn Ihrem E-Mail-Postfach finden Sie weitere Informationen darüber, wie Sie eine Löschbeanstandung einreichen können."; /* Error message when trying to login and the account is blocked due to any type of suspension, but copyright suspension */ -"account.suspension.message.nonCopyright"="Your account was terminated due to a breach of MEGA’s Terms of Service.\n\nYou will not be able to regain access to your stored data or be authorised to register a new MEGA account."; +"account.suspension.message.nonCopyright"="Ihr Account wurde aufgrund eines Verstoßes gegen die Nutzungsbedingungen von MEGA gekündigt.\n\nSie können nicht mehr auf Ihre gespeicherten Daten zugreifen und sind nicht berechtigt, einen neuen MEGA-Account zu registrieren."; /* Text shown in extensions to notify users that they have to open the application first */ "extensions.OpenApp.Message"="Öffnen Sie die MEGA-App und melden Sie sich an, um fortzufahren."; /* Account Storage title label of used storage size */ @@ -3175,21 +3175,21 @@ /* Chat Meetings tab - Section header title for the chat listing screen */ "chat.listing.sectionHeader.pastMeetings.title"="Vergangene Meetings"; /* Context menu item. Allows the user to verify contacts of the shared folder. The %@ placeholder will be replaced with the contact's name */ -"general.menuAction.verifyContact.title"="Approve %@"; +"general.menuAction.verifyContact.title"="%@ bestätigen"; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address that needs to be verified */ -"sharedItems.contactVerification.section.verifyContact.owner.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you share information with before they can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.owner.message"="Wir schützen Ihre Daten mit einer Zero-Knowledge-Verschlüsselung. Um eine besonders hohe Sicherheit zu gewährleisten, bitten wir Sie, die Kontakte, mit denen Sie Informationen austauschen, zu bestätigen, bevor diese auf die freigegebenen Ordner zugreifen können."; /* On fingerprint verification screen for Shared Items. Header message located at the top of "My credentials" section */ -"sharedItems.contactVerification.section.myCredentials.message"="To approve your contact, ensure the credentials you see above match their account credentials. You can ask them to share their credentials with you."; +"sharedItems.contactVerification.section.myCredentials.message"="Um Ihren Kontakt zu bestätigen, stellen Sie sicher, dass die oben angezeigten Identitätsdaten mit den Account-Identitätsdaten übereinstimmen. Sie können Ihren Kontakt bitten, Ihnen seine Identitätsdaten mitzuteilen."; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address if the owner of the received shared item is unverified */ -"sharedItems.contactVerification.section.verifyContact.receiver.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you receive information from before you can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.receiver.message"="Wir schützen Ihre Daten mit einer Zero-Knowledge-Verschlüsselung. Um eine besonders hohe Sicherheit zu gewährleisten, bitten wir Sie, die Kontakte, von denen Sie Informationen erhalten, zu bestätigen, bevor Sie auf die freigegebenen Ordner zugreifen können."; /* On fingerprint verification screen for Incoming Shared Items. Yellow banner message that will be shown to the receiver if the owner of the folder haven't verified them yet. */ "sharedItems.contactVerification.section.verifyContact.bannerMessage"=""; /* On Cloud Drive screen for incoming shared folder, if the contact which shared the folder is not verified, this is the message that wwe display */ -"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@ is shared by a contact you haven’t approved. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@ wurde von einem Kontakt freigegeben, den Sie noch nicht bestätigt haben. Um eine besonders hohe Sicherheit zu gewährleisten, empfehlen wir Ihnen, ihre Anmeldedaten in Ihren Kontakten zu bestätigen, indem Sie auf das Symbol ⓘ neben dem zu überprüfenden Kontakt tippen."; /* Title of the label in verification screen. It shows the credentials of the current user so it can be used to be verified by other contacts */ "verifyCredentials.yourCredentials.title"="Ihre Identitätsdaten"; /* Title of the header label in contact verification screen. */ -"verifyCredentials.headerMessage"="We protect your data with zero-knowledge encryption so all the information you store, share, and receive on MEGA is secure. To ensure extra security, we ask you to approve the contacts you share information with or receive data from."; +"verifyCredentials.headerMessage"="Wir schützen Ihre Daten mit einer Zero-Knowledge-Verschlüsselung, um die Sicherheit aller Informationen zu gewährleisten, die Sie auf MEGA speichern, freigeben oder empfangen. Um eine besonders hohe Sicherheit zu gewährleisten, bitten wir Sie, die Kontakte, für die Sie Informationen freigeben oder von denen Sie Daten erhalten, zu bestätigen."; /* On Incoming Shared Items Tab. Name of the folder if the the owner and the receiver has not yet verified each other. */ "sharedItems.tab.incoming.undecryptedFolderName"="[Nicht entschlüsselter Ordner]"; /* On Outgoing Shared Items Tab. Text that shows the receiver's name of the shared folder. %@ will be replaced with the receiver's name. */ @@ -3317,9 +3317,9 @@ /* Text description for a meeting showing that is a monthly recurring meeting */ "meetings.scheduled.recurring.monthly"="jeden Monat"; /* On Outgoing Shared Items Tab. Title of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ -"sharedItems.tab.outgoing.modal.cannotVerifyContact.title"="Cannot approve contact"; +"sharedItems.tab.outgoing.modal.cannotVerifyContact.title"="Kontakt kann nicht bestätigt werden"; /* On Outgoing Shared Items Tab. Message of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ -"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="You can’t approve %@ as they’re not in your contact list. Wait for them to accept your invitation first."; +"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="Sie können %@ nicht bestätigen, da diese Person nicht in Ihrer Kontaktliste steht. Warten Sie, bis Ihre Einladung angenommen wird."; /* Title shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled */ "picker.disable.passcode.title"="MEGA-Passcode deaktivieren"; /* Description shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled, it shows the users how to disable the passcode */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Blockiert"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Unbekanntes Gerät"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Im Cloud Drive anzeigen"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="Wiederholung beenden"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3641,11 +3645,11 @@ /* Album failed to save to cloud drive alert message */ "albumLink.alert.message.albumFailedToSaveToCloudDrive"="„%@“ kann nicht im Cloud Drive gespeichert werden. Versuchen Sie es später erneut. Wenn das Problem weiterhin besteht, wenden Sie sich an die Person, die Ihnen den Link mitgeteilt hat."; /* Text displayed on banner we show when sharing folder with unverified contacts */ -"shareFolder.contactsNotVerified"="Some of the contacts you’re sharing information with haven’t been approved by you. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"shareFolder.contactsNotVerified"="Sie haben einige Kontakte, mit denen Sie Informationen teilen, noch nicht bestätigt. Um eine besonders hohe Sicherheit zu gewährleisten, empfehlen wir Ihnen, ihre Anmeldedaten in Ihren Kontakten zu bestätigen, indem Sie auf das Symbol ⓘ neben dem zu überprüfenden Kontakt tippen."; /* Rename public album alert title */ "albumLink.alert.renameAlbum.title"="Album umbenennen"; /* Rename public album alert message */ -"albumLink.alert.renameAlbum.message"="An album named “%@” already exists in your Albums. Enter a new name for the album you’re saving."; +"albumLink.alert.renameAlbum.message"="Sie haben bereits ein Album namens „%@“. Geben Sie einen neuen Namen für das zu speichernde Album ein."; /* Button title of the dialog for transfer quota error that will direct to upgrade plan */ "transferQuotaError.button.upgrade"="Upgrade"; /* Button title of the dialog for transfer quota error that will dismiss dialog */ @@ -3653,7 +3657,7 @@ /* Button title of the dialog for transfer quota error to buy new plan */ "transferQuotaError.button.buyNewPlan"="Neues Paket kaufen"; /* Footer message of the dialog for transfer quota error. [A] will be replaced by the usage quota percent. [B] will be replaced by the max transfer quota in TB. For example: 100% of 16 TB used */ -"transferQuotaError.footerMessage.quotaUsage"="[A]%% of [B] TB used"; +"transferQuotaError.footerMessage.quotaUsage"="[A]%% von [B] TB verbraucht"; /* Title of the dialog for transfer quota error with limited available trasfer quota */ "transferQuotaError.downloadLimitedQuota.title"="Begrenztes Transfervolumen verfügbar"; /* Message text of the dialog for transfer quota error with limited available trasfer quota for free accounts */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Beitreten"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Bitte warten Sie, bis der Gastgeber Sie hereinlässt"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Bitte warten Sie, bis der Gastgeber Sie hereinlässt"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Bitte warten Sie, bis der Gastgeber das Meeting startet"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="Der Gastgeber hat Sie nicht hereingelassen"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="Alles klar"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ wartet darauf, der Konferenz beizutreten"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Ablehnen"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Hereinlassen"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="Warteraum anzeigen"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Alle hereinlassen"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="%@ den Beitritt verweigern?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Beitritt verweigern"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Abbrechen"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ wartet darauf, „%@“ beizutreten"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="In MEGA öffnen"; \ No newline at end of file diff --git a/iMEGA/Languages/de.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/de.lproj/Localizable.stringsdict similarity index 98% rename from iMEGA/Languages/de.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/de.lproj/Localizable.stringsdict index 55b2bd9802..8db231b891 100644 --- a/iMEGA/Languages/de.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/de.lproj/Localizable.stringsdict @@ -2653,9 +2653,9 @@ NSStringFormatValueTypeKey d one - Every month on day [cardinalNumber] + Jeden Monat am [cardinalNumber]. other - Every %d months on day [cardinalNumber] + Alle %d Monate am [cardinalNumber]. meetings.scheduled.create.monthly.weekNumberAndWeekDay.selectedFrequency @@ -2706,5 +2706,37 @@ %d Dateien im Cloud Drive gespeichert + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Download gestartet + other + %d Downloads gestartet + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/iMEGA/Languages/en.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/en.lproj/Localizable.strings similarity index 98% rename from iMEGA/Languages/en.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/en.lproj/Localizable.strings index b98eeaec09..5b821f27e1 100644 --- a/iMEGA/Languages/en.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/en.lproj/Localizable.strings @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="You have previously subscribed to a Pro plan with Google Play or AppGallery. Please manually cancel your subscription with them inside Google Play or the Huawei AppGallery on your device and then retry."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Do you want to cancel your current subscription and continue with the purchase?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Not available with your current plan"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Active subscription"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Update"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="The file will be updated with a new version."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Apply to all %@ duplicates"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="You’re the only one here"; /* Meetings end call dialog description */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Blocked"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Unknown device"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Show in Cloud drive"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="End recurrence"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Join"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wait for host to let you in"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wait for host to let you in"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Wait for host to start the meeting"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="Host didn’t let you in"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="OK, got it"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ is waiting to join the call"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Deny"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Admit"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"="%@ participants are waiting to join the call"; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="See waiting room"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Admit all"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Deny %@ entry?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Deny entry"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Cancel"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ is waiting to join “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"="%@ participants are waiting to join “%@”"; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Open in MEGA"; diff --git a/iMEGA/Languages/en.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/en.lproj/Localizable.stringsdict similarity index 99% rename from iMEGA/Languages/en.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/en.lproj/Localizable.stringsdict index dd3e871ee8..638d2bdfc3 100644 --- a/iMEGA/Languages/en.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/en.lproj/Localizable.stringsdict @@ -2706,5 +2706,37 @@ %d files saved to Cloud drive + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Download started + other + %d downloads started + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/iMEGA/Languages/es.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/es.lproj/Localizable.strings similarity index 97% rename from iMEGA/Languages/es.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/es.lproj/Localizable.strings index a14209e72a..506f4713a2 100644 --- a/iMEGA/Languages/es.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/es.lproj/Localizable.strings @@ -853,7 +853,7 @@ /* 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 */ -"clearOfflineFiles"="Eliminar los archivos sin conexión"; +"clearOfflineFiles"="Eliminar archivos sin conexión"; /* Question shown after you tap on 'Settings' - 'File Management' - 'Clear Offline files' to confirm the action */ "settings.fileManagement.alert.clearAllOfflineFiles"="¿Eliminar todos los archivos sin conexión?"; /* Section title where you can 'Clear Cache' of your MEGA app */ @@ -1207,7 +1207,7 @@ /* Title of a warning recommending upgrade to Pro */ "Upgrade to Pro"="Ampliar cuenta a Pro"; /* A description of MEGA features available only with a Pro plan. */ -"Access Pro only features like setting password protection and expiry dates for public files."="Access Pro only features like setting password protection and expiry dates for public links."; +"Access Pro only features like setting password protection and expiry dates for public files."="Accede a las características de una cuenta Pro, como configurar la protección por contraseña o la fecha de caducidad para enlaces públicos."; /* Alert title shown when the DEBUG mode is enabled */ "enableDebugMode_title"="Activar modo debug"; /* Alert message shown when the DEBUG mode is enabled */ @@ -1849,7 +1849,7 @@ /* Message show when a user cannot activate the video in a group call because the max number of videos has been reached */ "Error. No more video are allowed in this group call."="No puedes activar el vídeo ya que se alcanzado el número máximo de participantes que usan el vídeo en esta llamada."; /* Error shown when trying to start a call in a group with more peers than allowed */ -"Unable to start a call because the participants limit was exceeded."="Unable to start the call due to the participant limit having been exceeded."; +"Unable to start a call because the participants limit was exceeded."="No se puede iniciar la llamada porque se ha superado el límite de participantes."; /* Label shown while joining a public chat */ "Joining..."="Entrando…"; /* Label shown while leaving a public chat */ @@ -1937,7 +1937,7 @@ /* 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"="Your upload cannot proceed because your Cloud storage is full"; +"Your upload(s) cannot proceed because your account is full"="La subida no puede continuar porque tu Nube 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...' */ @@ -2035,7 +2035,7 @@ /* text of the alert dialog when the user is changing the API URL to staging */ "Are you sure you want to change to a test server? Your account may suffer irrecoverable problems"="¿Confirmas que quieres cambiar a un servidor de prueba? Tu cuenta podría sufrir daños irreparables."; /* Title label that explains that the user is going to be asked for the contacts permission */ -"Enable Access to Your Address Book"="Permitir el acceso a tus contactos"; +"Enable Access to Your Address Book"="Conceder el acceso a tus contactos"; /* Text indicating the user to open the device settings for MEGA */ "Open Settings"="Abrir Configuración"; /* Text encouraging the user to invite contacts to MEGA */ @@ -2047,7 +2047,7 @@ /* Text used as a section title or similar */ "Enter Email"="Añadir correo electrónico"; /* Text to send as SMS message to user contacts inviting them to MEGA */ -"contact.invite.message"="You have a MEGA Chat request waiting. Register an account on MEGA and get 20 GB free lifetime storage."; +"contact.invite.message"="Tienes una solicitud en espera para chatear en MEGA. Registra una cuenta en MEGA y obtén 20 GB de almacenamiento gratuito para siempre."; /* Destination folder name of chat files */ "My chat files"="Mis archivos de chat"; /* Title of the section about the plan in the storage tab in My Account Section */ @@ -2585,11 +2585,11 @@ /* Over Disk Quota paywall not final detail message. */ "dialog.storage.paywall.notFinal.detail"="Lamentablemente no has realizado ninguna acción en los últimos %@ desde que te avisamos por correo electrónico a la dirección %@, el %@. Todavía estás usando %@ de almacenamiento, y estás por encima de tu límite gratuito. Consulta los correos electrónicos que te enviamos para obtener más información sobre lo que debes hacer."; /* Over Disk Quota paywall final detail message. */ -"dialog.storage.paywall.final.detail"="Your data will be deleted tomorrow if your account has not been upgraded."; +"dialog.storage.paywall.final.detail"="Si no amplías la cuenta, tus datos serán eliminados mañana."; /* Over Disk Quota paywall, not final warning label. */ -"dialog.storage.paywall.notFinal.warning"="Upgrade within %@ to avoid your data getting deleted."; +"dialog.storage.paywall.notFinal.warning"="Amplía tu cuenta dentro de %@ para evitar que tus datos sean eliminados."; /* Over Disk Quota paywall, final warning label. */ -"dialog.storage.paywall.final.warning"="Upgrade today if you wish to keep your data."; +"dialog.storage.paywall.final.warning"="Amplía tu cuenta hoy si quieres conservar tus datos."; /* Button title to see the available bonus */ "general.button.getBonus"="Obtén bono"; /* Backup setup warning title */ @@ -2599,7 +2599,7 @@ /* Change the default backup location warning title. %@ is a folder */ "dialog.backup.folder.location.warning.title"="Mover “%@”"; /* Change the default backup location warning message */ -"dialog.root.backup.folder.location.warning.message"="You are changing a default backup folder location. This may affect your ability to find your backup folder. Please remember where it is located so that you can find it in the future."; +"dialog.root.backup.folder.location.warning.message"="Vas a cambiar la ubicación de tu carpeta de backup, y esto puede afectar tu capacidad para encontrarla. Recuerda la nueva ubicación para que puedas encontrarla en el futuro."; /* Change the default backup location warning message */ "dialog.backup.folder.location.warning.message"="Al mover esta carpeta se cambiará el destino de los backups y se desactivará esta función. ¿Es esto lo que quieres hacer? Puedes volver a activar los backups a través de la aplicación de escritorio de MEGA."; /* Delete the default backup folder warning title. %@ is a folder */ @@ -2643,7 +2643,7 @@ /* Shown when an invalid/inexisting/not-available-anymore meeting link is opened. */ "meetings.alert.end"="La reunión ha terminado"; /* Meeting ended Alert button -- View Meeting Chat history. */ -"meetings.alert.meetingchat"="View chat history"; +"meetings.alert.meetingchat"="Ver historial del chat"; /* Shown description when an invalid/inexisting/not-available-anymore meeting link is opened. */ "meetings.alert.end.description"="La reunión a la que intentas unirte ya ha finalizado. Aún puedes ver el historial de chat de la reunión."; /* Contacts selection screen header: Invite contacts to meetings */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="Estás subscrito a un plan Pro en Google Play o AppGallery. Cancela manualmente esa subscripción desde tu dispositivo a través de Google Play o de la AppGallery de Huawei y vuelve a intentarlo."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="¿Quieres cancelar tu suscripción actual y continuar con la compra?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="No disponible con tu plan actual"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Suscripción activa"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Actualizar"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="El archivo se subirá con una nueva versión."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Aplicar a los %@ duplicados"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="Solo estás tú aquí"; /* Meetings end call dialog description */ @@ -3153,9 +3153,9 @@ /* Start conversation: Schedule meeting context menu */ "meetings.startConversation.contextMenu.scheduleMeeting"="Programar reunión"; /* Error message when trying to login and the account is blocked due to copyright violation */ -"account.suspension.message.copyright"="Your MEGA account has been suspended due to repeated allegations of copyright infringements. This means you cannot access your account or data within it.\n\nCheck your email for more information on how to file a counter-notice."; +"account.suspension.message.copyright"="Tu cuenta de MEGA ha sido suspendida debido a repetidas notificaciones de presuntas infracciones de derechos de autor. Como consecuencia, no podrás acceder a tu cuenta ni a tus datos.\n\nRevisa el correo electrónico que te enviamos para obtener más información sobre cómo presentar una contranotificación."; /* Error message when trying to login and the account is blocked due to any type of suspension, but copyright suspension */ -"account.suspension.message.nonCopyright"="Your account was terminated due to a breach of MEGA’s Terms of Service.\n\nYou will not be able to regain access to your stored data or be authorised to register a new MEGA account."; +"account.suspension.message.nonCopyright"="Hemos cerrado tu cuenta debido a un incumplimiento de los Términos de servicio de MEGA.\n\nNo podrás recuperar el acceso a tus datos ni estás autorizado a registrar una nueva cuenta de MEGA."; /* Text shown in extensions to notify users that they have to open the application first */ "extensions.OpenApp.Message"="Abre la app de MEGA e inicia sesión para continuar."; /* Account Storage title label of used storage size */ @@ -3175,21 +3175,21 @@ /* Chat Meetings tab - Section header title for the chat listing screen */ "chat.listing.sectionHeader.pastMeetings.title"="Reuniones anteriores"; /* Context menu item. Allows the user to verify contacts of the shared folder. The %@ placeholder will be replaced with the contact's name */ -"general.menuAction.verifyContact.title"="Approve %@"; +"general.menuAction.verifyContact.title"="Aprobar %@"; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address that needs to be verified */ -"sharedItems.contactVerification.section.verifyContact.owner.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you share information with before they can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.owner.message"="Protegemos tus datos con cifrado de conocimiento cero. Para garantizar una mayor seguridad, te pedimos que apruebes los contactos con los que compartas información antes de que puedan acceder a las carpetas compartidas."; /* On fingerprint verification screen for Shared Items. Header message located at the top of "My credentials" section */ -"sharedItems.contactVerification.section.myCredentials.message"="To approve your contact, ensure the credentials you see above match their account credentials. You can ask them to share their credentials with you."; +"sharedItems.contactVerification.section.myCredentials.message"="Para aprobar tu contacto, asegúrate de que las credenciales que ves arriba coincidan con las credenciales de su cuenta. Puedes pedirle que comparta sus credenciales contigo."; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address if the owner of the received shared item is unverified */ -"sharedItems.contactVerification.section.verifyContact.receiver.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you receive information from before you can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.receiver.message"="Protegemos tus datos con cifrado de conocimiento cero. Para garantizar una mayor seguridad, te pedimos que apruebes los contactos de los que recibas información antes de que puedas acceder a las carpetas compartidas."; /* On fingerprint verification screen for Incoming Shared Items. Yellow banner message that will be shown to the receiver if the owner of the folder haven't verified them yet. */ "sharedItems.contactVerification.section.verifyContact.bannerMessage"=""; /* On Cloud Drive screen for incoming shared folder, if the contact which shared the folder is not verified, this is the message that wwe display */ -"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@ is shared by a contact you haven’t approved. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@ está siendo compartida por un contacto que no has aprobado. Para más seguridad, te recomendamos que apruebes sus credenciales en Contactos tocando el símbolo ⓘ junto al contacto que deseas aprobar."; /* Title of the label in verification screen. It shows the credentials of the current user so it can be used to be verified by other contacts */ "verifyCredentials.yourCredentials.title"="Tus credenciales"; /* Title of the header label in contact verification screen. */ -"verifyCredentials.headerMessage"="We protect your data with zero-knowledge encryption so all the information you store, share, and receive on MEGA is secure. To ensure extra security, we ask you to approve the contacts you share information with or receive data from."; +"verifyCredentials.headerMessage"="Protegemos tus datos con cifrado de conocimiento cero para que toda la información que almacenes, compartas y recibas en MEGA esté segura. Para más seguridad, te pedimos que apruebes los contactos con los que compartes o recibes datos."; /* On Incoming Shared Items Tab. Name of the folder if the the owner and the receiver has not yet verified each other. */ "sharedItems.tab.incoming.undecryptedFolderName"="[Carpeta sin descifrar]"; /* On Outgoing Shared Items Tab. Text that shows the receiver's name of the shared folder. %@ will be replaced with the receiver's name. */ @@ -3317,9 +3317,9 @@ /* Text description for a meeting showing that is a monthly recurring meeting */ "meetings.scheduled.recurring.monthly"="mes"; /* On Outgoing Shared Items Tab. Title of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ -"sharedItems.tab.outgoing.modal.cannotVerifyContact.title"="Cannot approve contact"; +"sharedItems.tab.outgoing.modal.cannotVerifyContact.title"="No se puede aprobar el contacto"; /* On Outgoing Shared Items Tab. Message of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ -"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="You can’t approve %@ as they’re not in your contact list. Wait for them to accept your invitation first."; +"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="No puedes aprobar %@ porque no está en tu lista de contactos. Espera a que acepte tu invitación primero."; /* Title shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled */ "picker.disable.passcode.title"="Desactivar código de acceso de MEGA"; /* Description shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled, it shows the users how to disable the passcode */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Bloqueado"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Dispositivo desconocido"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Mostrar en la Nube"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="Dejar de repetir"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3641,7 +3645,7 @@ /* Album failed to save to cloud drive alert message */ "albumLink.alert.message.albumFailedToSaveToCloudDrive"="“%@” no se ha podido guardar en la Nube. Vuelve a intentarlo más tarde y, si el problema persiste, contacta con la persona que te ha compartido el enlace."; /* Text displayed on banner we show when sharing folder with unverified contacts */ -"shareFolder.contactsNotVerified"="Some of the contacts you’re sharing information with haven’t been approved by you. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"shareFolder.contactsNotVerified"="Todavía no has aprobado algunos de los contactos con los que compartes información. Para más seguridad, te recomendamos que apruebes sus credenciales en Contactos tocando el símbolo ⓘ junto al contacto que deseas aprobar."; /* Rename public album alert title */ "albumLink.alert.renameAlbum.title"="Renombrar álbum"; /* Rename public album alert message */ @@ -3675,7 +3679,7 @@ /* Meeting waiting room leave alert message */ "meetings.waitingRoom.alert.leaveMeeting"="¿Quieres abandonar la reunión?"; /* Meeting waiting room don't leave button */ -"meetings.waitingRoom.alert.dontLeave"="Don’t leave"; +"meetings.waitingRoom.alert.dontLeave"="No abandonar"; /* Meeting waiting room guest join first name textfield */ "meetings.waitingRoom.guest.firstName"="Nombre"; /* Meeting waiting room guest join last name textfield */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Únete"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wait for host to let you in"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Espera a que el anfitrión te deje entrar"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Espera a que el anfitrión empiece la reunión"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="El anfitrión no te ha dejado entrar"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="Entendido"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ is waiting to join the call"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Denegar"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Admitir"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="See waiting room"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Admitir a todos"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Deny %@ entry?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Deny entry"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Cancelar"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ is waiting to join “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Open in MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/es.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/es.lproj/Localizable.stringsdict similarity index 99% rename from iMEGA/Languages/es.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/es.lproj/Localizable.stringsdict index bbaf3f960d..025df2619a 100644 --- a/iMEGA/Languages/es.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/es.lproj/Localizable.stringsdict @@ -2983,9 +2983,11 @@ NSStringFormatValueTypeKey d one - Every month on day [cardinalNumber] + El día [cardinalNumber] de cada mes + many + El día [cardinalNumber] cada %d meses other - Every %d months on day [cardinalNumber] + El día [cardinalNumber] cada %d meses meetings.scheduled.create.monthly.weekNumberAndWeekDay.selectedFrequency @@ -3042,5 +3044,37 @@ Se han guardado %d archivos en la Nube + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Download started + other + %d downloads started + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/iMEGA/Languages/fr.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/fr.lproj/Localizable.strings similarity index 98% rename from iMEGA/Languages/fr.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/fr.lproj/Localizable.strings index 9617689cd2..5eb092379c 100644 --- a/iMEGA/Languages/fr.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/fr.lproj/Localizable.strings @@ -1821,7 +1821,7 @@ /* Title shown in a cell to allow the users enable the 'Encrypted Key Rotation' */ "Enable Encrypted Key Rotation"="Activer la rotation de la clé de chiffrement"; /* Label in a cell where you can get the chat link */ -"Get Chat Link"="Obtenir le lien de conversation"; +"Get Chat Link"="Obtenir le lien de la conversation"; /* Footer text to explain what means 'Encrypted Key Rotation' */ "Key rotation is slightly more secure, but does not allow you to create a chat link and new participants will not see past messages."="La rotation de la clé de chiffrement est un peu plus sûre, mais elle ne vous permet pas de créer un lien de conversation, et les nouveaux participants ne verront pas les anciens messages."; /* Footer to explain why key rotation is disabled for public chats with many participants */ @@ -1841,7 +1841,7 @@ /* Management message shown in a chat when the user %@ creates a public link for the chat */ "%@ created a public link for the chat."="%@ a créé un lien de conversation"; /* Management message shown in a chat when the user %@ removes a public link for the chat */ -"%@ removed a public link for the chat."="%@ a supprimé le lien de conversation"; +"%@ removed a public link for the chat."="%@ a supprimé le lien de la conversation"; /* Management message shown in a chat when the user %@ joined it from a public chat link */ "%@ joined the group chat."="%@ fait partie de la conversation de groupe."; /* Message show when a call cannot be established because there are too many participants in the group call */ @@ -2009,7 +2009,7 @@ /* Message shown when the user has not recent activity in their account. */ "No recent activity"="Aucune activité récente"; /* Shown when an invalid/inexisting/not-available-anymore chat link is opened. */ -"Chat Link Unavailable"="Le lien de conversation n’est pas disponible"; +"Chat Link Unavailable"="Le lien de la conversation est inaccessible"; /* Shown when an inexisting/unavailable/removed link is tried to be opened. */ "This chat link is no longer available"="Ce lien de conversation n’est plus accessible"; /* Text explaining users how the chat links work. */ @@ -2017,7 +2017,7 @@ /* Subtitle shown in a chat to inform where to tap to enter in the chat details view */ "Tap here for info"="Touchez ici pour obtenir des renseignements"; /* In some cases, a user may try to get the link for a chat room, but if such is not set by an operator - it would say "not link available" and not auto create it. */ -"No chat link available."="Aucun lien de conversation n’est disponible."; +"No chat link available."="Aucun lien de conversation n’est proposé"; /* Title of a dialog in which we request access to a specific permission, like the Location Services */ "Please allow access"="Veuillez accorder l’accès"; /* Hint shown to the users, when they want to use the Location Services but they are disabled or restricted for MEGA */ @@ -2055,13 +2055,13 @@ /* */ "If you lose this Recovery key and forget your password, [B]all your files, folders and messages will be inaccessible, even by MEGA[/B]."="Si vous perdez votre Clé de récupération et oubliez votre mot de passe, [B]tous vos fichiers, dossiers et messages seront inaccessibles, même pour MEGA[/B]."; /* */ -"Business"="Pour entreprise"; +"Business"="Pour entreprise"; /* */ "Administrator"="Administrateur"; /* Label to indicate the amount of transfer quota in several places. It is a ‘noun‘ and there is an screenshot with an use example - (String as short as possible). */ "Transfer"="Transfert"; /* Error shown when a Business account user (sub-user or admin) tries to remove a contact which is part of the same Business account. Please, keep the placeholder, it will be replaced with the name or email of the account, for example: Jane Appleseed or ja@mega.nz */ -"You cannot remove %1$s as a contact because they are part of your Business account."="Vous ne pouvez pas supprimer %1$s de vos contacts, car ce contact fait parti de votre compte Pour entreprise."; +"You cannot remove %1$s as a contact because they are part of your Business account."="Vous ne pouvez pas supprimer %1$s de vos contacts, car ce contact fait parti de votre compte Pour entreprise."; /* Label indicating that the passcode (pin) view will be displayed if the application goes back to foreground after being x time in background. Examples: require passcode immediately, require passcode after 5 minutes. */ "Require Passcode"="Exiger le code d’accès"; /* Label indicating that the enter passcode (pin) view will be displayed immediately if the application goes back to foreground after being in background. */ @@ -2079,11 +2079,11 @@ /* A dialog title shown to users when their business account is expired. */ "Your business account is expired"="Le compte est désactivé"; /* Details shown when a Business account is expired. Details for the administrator of the Business account */ -"There has been a problem processing your payment. MEGA is limited to view only until this issue has been fixed in a desktop web browser."="Votre compte Pour entreprise a été désactivé pour défaut de paiement. Vous ne pourrez plus accéder aux données stockées dans votre compte. Pour effectuer un paiement et réactiver votre abonnement, connectez-vous à MEGA dans un navigateur."; +"There has been a problem processing your payment. MEGA is limited to view only until this issue has been fixed in a desktop web browser."="Votre compte Pour entreprise a été désactivé pour défaut de paiement. Vous ne pourrez plus accéder aux données stockées dans votre compte. Pour effectuer un paiement et réactiver votre abonnement, connectez-vous à MEGA dans un navigateur."; /* A dialog message which is shown to sub-users of expired business accounts. */ "Your account is currently [B]suspended[/B]. You can only browse your data."="Votre compte est actuellement [B]désactivé[/B]. Vous ne pouvez que parcourir vos données."; /* Message shown when users with a business account (no administrators of a business account) try to enable the Camera Uploads, to advise them that the administrator do have the ability to view their data. */ -"While MEGA does not have access to your data, your organization administrators do have the ability to control and view the Camera Uploads in your user account"="MEGA ne peut pas accéder à vos données. Cependant, l’administrateur de votre compte Pour entreprise peut accéder à vos Téléversements de l’appareil photo."; +"While MEGA does not have access to your data, your organization administrators do have the ability to control and view the Camera Uploads in your user account"="MEGA ne peut pas accéder à vos données. Cependant, l’administrateur de votre compte Pour entreprise peut accéder à vos Téléversements de l’appareil photo."; /* */ "Something went wrong"="Quelque chose a mal tourné"; /* Title of the "Links" Shared Items. */ @@ -2095,7 +2095,7 @@ /* Title of the screen that shows the users with whom the user can share a folder */ "Share with"="Partager avec"; /* A dialog message which is shown to sub-users of expired business accounts. */ -"Contact your business account administrator to resolve the issue and activate your account."="Contactez l’administrateur de votre compte Pour entreprise pour résoudre la situation et activer votre compte."; +"Contact your business account administrator to resolve the issue and activate your account."="Contactez l’administrateur de votre compte Pour entreprise pour résoudre la situation et activer votre compte."; /* Chat Notifications DND: Option to turn the DND on until this morning 8 AM */ "Until this morning"="Jusqu’à ce matin"; /* Chat Notifications DND: Option to turn the DND on until tomorrow morning 8 AM */ @@ -2227,9 +2227,9 @@ /* file type title, used in changing the export format of scaned doc */ "File Type"="Type de fichier"; /* Error message appears to sub-users of a business account when they try to login and they are disabled. */ -"Your account has been disabled by your administrator. Please contact your business account administrator for further details."="Votre compte a été désactivé par votre administrateur. Veuillez contacter l’administrateur de votre compte Pour entreprise pour obtenir plus de précisions."; +"Your account has been disabled by your administrator. Please contact your business account administrator for further details."="Votre compte a été désactivé par votre administrateur. Veuillez contacter l’administrateur de votre compte Pour entreprise pour obtenir plus de précisions."; /* An error message which appears to sub-users of a business account when they try to login and they are deleted. */ -"Your account has been removed by your administrator. Please contact your business account administrator for further details."="Votre compte a été supprimé par votre administrateur. Veuillez contacter l’administrateur de votre compte Pour entreprise pour plus de précisions."; +"Your account has been removed by your administrator. Please contact your business account administrator for further details."="Votre compte a été supprimé par votre administrateur. Veuillez contacter l’administrateur de votre compte Pour entreprise pour plus de précisions."; /* Title of the dialog shown when the user it is creating a chat link and the chat has not title */ "Enter group name"="Saisissez le nom du groupe"; /* Chat: This is the placeholder text for text view when keyboard is shown */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="Vous avez souscrit auparavant un abonnement Pro grâce aux magasins Play Store de Google ou AppGalery de Huawei. Veuillez annuler vos abonnements manuellement sur votre appareil dans les magasins Play Store de Google ou AppGalery de Huawei, puis réessayez."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Voulez-vous annuler l’abonnement actuel et poursuivre l’achat ?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="N’est pas proposé avec votre abonnement actuel"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Un abonnement est actif"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Mettre à jour"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="Le fichier sera mis à jour par une nouvelle version."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Appliquer à tous les %@ doublons"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="Vous êtes la seule personne ici"; /* Meetings end call dialog description */ @@ -3213,7 +3213,7 @@ /* Title for a button to send a file to a chat */ "meetings.info.shareOptions.sendToChat"="Envoyer vers une conversation"; /* Title for share chat link option in meeting info */ -"meetings.info.shareChatLink"="Partager le lien de conversation"; +"meetings.info.shareChatLink"="Partager le lien de la conversation"; /* Button title to load more participants in meeting info view */ "meetings.info.participants.seeMore"="Afficher davantage"; /* Button title to see less participants in meeting info view */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Bloqué"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Appareil inconnu"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Afficher dans le Disque nuagique"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Afficher dans Sauvegardes"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="Fin de la récurrence"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Me joindre"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Attendre que l’hôte vous accueille"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Attendre que l’hôte vous accueille"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Attendre que l’hôte commence la réunion"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="L’hôte ne vous a pas accueilli"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="Vous quitterez la salle d’attente."; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="D’accord, j’ai compris"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ attend de se joindre à l’appel"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Refuser"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="L’accueillir"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="Afficher la salle d’attente"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Les accueillir"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Refuser l’entrée à %@ ?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Refuser l’entrée"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Annuler"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ attend de se joindre à « %@ »"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Ouvrir dans MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/fr.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/fr.lproj/Localizable.stringsdict similarity index 99% rename from iMEGA/Languages/fr.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/fr.lproj/Localizable.stringsdict index 47c3f289d8..d9c551483f 100644 --- a/iMEGA/Languages/fr.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/fr.lproj/Localizable.stringsdict @@ -3044,5 +3044,41 @@ %d fichiers ont été enregistrés dans le Disque nuagique. + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Le téléchargement est lancé + many + %d de téléchargements sont lancés + other + %d téléchargements sont lancés + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d élément a été sélectionné. + many + %d d’éléments ont été sélectionnés + other + %d éléments ont été sélectionnés + + \ No newline at end of file diff --git a/iMEGA/Languages/id.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/id.lproj/Localizable.strings similarity index 97% rename from iMEGA/Languages/id.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/id.lproj/Localizable.strings index a0d296c65d..e53689dbc4 100644 --- a/iMEGA/Languages/id.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/id.lproj/Localizable.strings @@ -1207,7 +1207,7 @@ /* Title of a warning recommending upgrade to Pro */ "Upgrade to Pro"="Upgrade ke Pro"; /* A description of MEGA features available only with a Pro plan. */ -"Access Pro only features like setting password protection and expiry dates for public files."="Access Pro only features like setting password protection and expiry dates for public links."; +"Access Pro only features like setting password protection and expiry dates for public files."="Akses hanya fitur Pro seperti mengatur perlindungan kata sandi dan tanggal kedaluwarsa untuk tautan publik."; /* Alert title shown when the DEBUG mode is enabled */ "enableDebugMode_title"="Nyalakan mode debug"; /* Alert message shown when the DEBUG mode is enabled */ @@ -1295,7 +1295,7 @@ /* Label for any ‘Verify’ button, link, text, title, etc. - (String as short as possible). */ "verify"="Setujui"; /* Button title */ -"verified"="Approved"; +"verified"="Disetujui"; /* A button title to delete the history of a chat. */ "clearChatHistory"="Hapus Riwayat Chat"; /* A confirmation message for a user to confirm that they want to clear the history of a chat. */ @@ -1849,7 +1849,7 @@ /* Message show when a user cannot activate the video in a group call because the max number of videos has been reached */ "Error. No more video are allowed in this group call."="Anda tidak diizinkan untuk mengaktifkan video karena panggilan ini telah mencapai jumlah maksimum peserta menggunakan video."; /* Error shown when trying to start a call in a group with more peers than allowed */ -"Unable to start a call because the participants limit was exceeded."="Unable to start the call due to the participant limit having been exceeded."; +"Unable to start a call because the participants limit was exceeded."="Tidak dapat memulai panggilan karena batas peserta terlampaui."; /* Label shown while joining a public chat */ "Joining..."="Bergabung…"; /* Label shown while leaving a public chat */ @@ -1937,7 +1937,7 @@ /* 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 melebihi batas penyimpanan anda saat ini"; /* uploads over storage quota warning dialog title */ -"Your upload(s) cannot proceed because your account is full"="Your upload cannot proceed because your Cloud storage is full"; +"Your upload(s) cannot proceed because your account is full"="Pengunggahan anda tidak dapat dilanjutkan karena penyimpanan Cloud 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."="Izinkan kontak Anda melihat terakhir kali Anda aktif di MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ @@ -2047,7 +2047,7 @@ /* Text used as a section title or similar */ "Enter Email"="Masukan Email"; /* Text to send as SMS message to user contacts inviting them to MEGA */ -"contact.invite.message"="You have a MEGA Chat request waiting. Register an account on MEGA and get 20 GB free lifetime storage."; +"contact.invite.message"="Anda memiliki permintaan Obrolan MEGA yang menunggu. Daftarkan akun di MEGA Chat dan dapatkan 20 GB penyimpanan gratis seumur hidup."; /* Destination folder name of chat files */ "My chat files"="File obrolan saya"; /* Title of the section about the plan in the storage tab in My Account Section */ @@ -2585,11 +2585,11 @@ /* Over Disk Quota paywall not final detail message. */ "dialog.storage.paywall.notFinal.detail"="Sayangnya anda belum mengambil tindakan di %@ sejak email kami dikirim ke %@, pada %@. Anda masih menggunakan %@ penyimpanan, yang melebihi batas gratis anda. Silakan lihat email yang kami kirimkan kepada anda untuk informasi lebih lanjut tentang apa yang perlu anda lakukan."; /* Over Disk Quota paywall final detail message. */ -"dialog.storage.paywall.final.detail"="Your data will be deleted tomorrow if your account has not been upgraded."; +"dialog.storage.paywall.final.detail"="Data anda akan dihapus besok jika akun anda belum ditingkatkan."; /* Over Disk Quota paywall, not final warning label. */ -"dialog.storage.paywall.notFinal.warning"="Upgrade within %@ to avoid your data getting deleted."; +"dialog.storage.paywall.notFinal.warning"="Tingkatkan dalam %@ untuk menghindari data anda terhapus."; /* Over Disk Quota paywall, final warning label. */ -"dialog.storage.paywall.final.warning"="Upgrade today if you wish to keep your data."; +"dialog.storage.paywall.final.warning"="Tingkatkan hari ini jika anda ingin menyimpan data anda."; /* Button title to see the available bonus */ "general.button.getBonus"="Dapatkan Bonus"; /* Backup setup warning title */ @@ -2599,7 +2599,7 @@ /* Change the default backup location warning title. %@ is a folder */ "dialog.backup.folder.location.warning.title"="Pindahkan “%@”"; /* Change the default backup location warning message */ -"dialog.root.backup.folder.location.warning.message"="You are changing a default backup folder location. This may affect your ability to find your backup folder. Please remember where it is located so that you can find it in the future."; +"dialog.root.backup.folder.location.warning.message"="Anda mengubah lokasi folder cadangan default. Hal ini mungkin memengaruhi kemampuan anda untuk menemukan folder cadangan. Harap ingat di mana lokasinya sehingga anda dapat menemukannya di masa mendatang."; /* Change the default backup location warning message */ "dialog.backup.folder.location.warning.message"="Memindahkan folder ini mengubah tujuan pencadangan. Pencadangan akan dimatikan demi keamanan. Apakah ini yang ingin anda lakukan? Pencadangan dapat diaktifkan kembali dengan Aplikasi Desktop MEGA."; /* Delete the default backup folder warning title. %@ is a folder */ @@ -2643,7 +2643,7 @@ /* Shown when an invalid/inexisting/not-available-anymore meeting link is opened. */ "meetings.alert.end"="Rapat Berakhir"; /* Meeting ended Alert button -- View Meeting Chat history. */ -"meetings.alert.meetingchat"="View chat history"; +"meetings.alert.meetingchat"="Lihat riwayat obrolan"; /* Shown description when an invalid/inexisting/not-available-anymore meeting link is opened. */ "meetings.alert.end.description"="Rapat yang anda coba ikuti telah berakhir. Anda masih dapat melihat riwayat obrolan rapat."; /* Contacts selection screen header: Invite contacts to meetings */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="Anda sebelumnya telah berlangganan paket Pro dengan Google Play atau AppGallery. Harap batalkan langganan anda secara manual dengan mereka di dalam Google Play atau Huawei AppGallery di perangkat anda, lalu coba lagi."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Apakah anda ingin membatalkan langganan anda saat ini dan melanjutkan pembelian?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Tidak tersedia dengan paket anda saat ini"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Berlangganan Aktif"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Perbarui"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="File akan diperbarui dengan versi baru."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Terapkan ke semua %@ duplikat "; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="Anda satu-satunya di sini"; /* Meetings end call dialog description */ @@ -3153,9 +3153,9 @@ /* Start conversation: Schedule meeting context menu */ "meetings.startConversation.contextMenu.scheduleMeeting"="Jadwalkan pertemuan"; /* Error message when trying to login and the account is blocked due to copyright violation */ -"account.suspension.message.copyright"="Your MEGA account has been suspended due to repeated allegations of copyright infringements. This means you cannot access your account or data within it.\n\nCheck your email for more information on how to file a counter-notice."; +"account.suspension.message.copyright"="Akun MEGA anda telah ditangguhkan karena tuduhan pelanggaran hak cipta berulang kali. Ini berarti anda tidak dapat mengakses akun atau data anda di dalamnya.\n\nPeriksa email anda untuk informasi lebih lanjut tentang cara mengajukan pemberitahuan tanggapan."; /* Error message when trying to login and the account is blocked due to any type of suspension, but copyright suspension */ -"account.suspension.message.nonCopyright"="Your account was terminated due to a breach of MEGA’s Terms of Service.\n\nYou will not be able to regain access to your stored data or be authorised to register a new MEGA account."; +"account.suspension.message.nonCopyright"="Akun anda dihentikan karena pelanggaran Ketentuan Layanan MEGA.\n\nAnda tidak akan bisa mendapatkan kembali akses ke data yang disimpan atau diberi wewenang untuk mendaftarkan akun MEGA baru."; /* Text shown in extensions to notify users that they have to open the application first */ "extensions.OpenApp.Message"="Buka aplikasi MEGA dan masuk untuk melanjutkan."; /* Account Storage title label of used storage size */ @@ -3175,21 +3175,21 @@ /* Chat Meetings tab - Section header title for the chat listing screen */ "chat.listing.sectionHeader.pastMeetings.title"="Pertemuan sebelumnya"; /* Context menu item. Allows the user to verify contacts of the shared folder. The %@ placeholder will be replaced with the contact's name */ -"general.menuAction.verifyContact.title"="Approve %@"; +"general.menuAction.verifyContact.title"="Disetujui %@"; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address that needs to be verified */ -"sharedItems.contactVerification.section.verifyContact.owner.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you share information with before they can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.owner.message"="Kami melindungi data anda dengan enkripsi tanpa pengetahuan. Untuk memastikan keamanan ekstra, kami meminta anda untuk menyetujui kontak yang anda bagikan informasi sebelum mereka dapat mengakses folder bersama."; /* On fingerprint verification screen for Shared Items. Header message located at the top of "My credentials" section */ -"sharedItems.contactVerification.section.myCredentials.message"="To approve your contact, ensure the credentials you see above match their account credentials. You can ask them to share their credentials with you."; +"sharedItems.contactVerification.section.myCredentials.message"="Untuk menyetujui kontak anda, pastikan kredensial yang anda lihat di atas cocok dengan kredensial akun mereka. Anda dapat meminta mereka untuk membagikan kredensialnya kepada anda."; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address if the owner of the received shared item is unverified */ -"sharedItems.contactVerification.section.verifyContact.receiver.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you receive information from before you can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.receiver.message"="Kami melindungi data anda dengan enkripsi tanpa pengetahuan. Untuk memastikan keamanan ekstra, kami meminta anda menyetujui kontak yang informasinya anda terima sebelum anda dapat mengakses folder bersama."; /* On fingerprint verification screen for Incoming Shared Items. Yellow banner message that will be shown to the receiver if the owner of the folder haven't verified them yet. */ "sharedItems.contactVerification.section.verifyContact.bannerMessage"=""; /* On Cloud Drive screen for incoming shared folder, if the contact which shared the folder is not verified, this is the message that wwe display */ -"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@ is shared by a contact you haven’t approved. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@ dibagikan oleh kontak yang belum anda setujui. Untuk memastikan keamanan ekstra, kami menyarankan anda menyetujui kredensial mereka di Kontak dengan mengetuk ⓘ di samping kontak yang ingin anda setujui."; /* Title of the label in verification screen. It shows the credentials of the current user so it can be used to be verified by other contacts */ "verifyCredentials.yourCredentials.title"="Kredensial Anda"; /* Title of the header label in contact verification screen. */ -"verifyCredentials.headerMessage"="We protect your data with zero-knowledge encryption so all the information you store, share, and receive on MEGA is secure. To ensure extra security, we ask you to approve the contacts you share information with or receive data from."; +"verifyCredentials.headerMessage"="Kami melindungi data anda dengan enkripsi tanpa pengetahuan sehingga semua informasi yang anda simpan, bagikan, dan terima di MEGA aman. Untuk memastikan keamanan ekstra, kami meminta anda untuk menyetujui kontak yang anda bagikan informasi atau terima datanya."; /* On Incoming Shared Items Tab. Name of the folder if the the owner and the receiver has not yet verified each other. */ "sharedItems.tab.incoming.undecryptedFolderName"="[Folder tidak terdekripsi]"; /* On Outgoing Shared Items Tab. Text that shows the receiver's name of the shared folder. %@ will be replaced with the receiver's name. */ @@ -3317,9 +3317,9 @@ /* Text description for a meeting showing that is a monthly recurring meeting */ "meetings.scheduled.recurring.monthly"="bulanan"; /* On Outgoing Shared Items Tab. Title of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ -"sharedItems.tab.outgoing.modal.cannotVerifyContact.title"="Cannot approve contact"; +"sharedItems.tab.outgoing.modal.cannotVerifyContact.title"="Tidak dapat menyetujui kontak"; /* On Outgoing Shared Items Tab. Message of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ -"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="You can’t approve %@ as they’re not in your contact list. Wait for them to accept your invitation first."; +"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="Anda tidak dapat menyetujui %@ karena mereka tidak ada dalam daftar kontak anda. Tunggu hingga mereka menerima undangan anda terlebih dahulu."; /* Title shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled */ "picker.disable.passcode.title"="Nonaktifkan kode sandi MEGA"; /* Description shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled, it shows the users how to disable the passcode */ @@ -3441,7 +3441,11 @@ /* Device Center backup blocked status */ "device.center.backup.blocked.status.message"="Terblokir"; /* Default device name in case the device name cannot be obtained */ -"device.center.default.device.title"="Unknown device"; +"device.center.default.device.title"="Perangkat tidak dikenal"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Tunjukkan di Cloud Drive"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="Akhiri pengulangan"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3611,41 +3615,41 @@ /* Edit Schedule meeting navigaion title. */ "meetings.scheduled.editMeeting.title"="Mengubah rapat"; /* Schedule meeting calendar invite description */ -"meetings.scheduleMeeting.calendarInvite.description"="Email a calendar invite to participants so they can add the meeting to their calendars."; +"meetings.scheduleMeeting.calendarInvite.description"="Kirimkan undangan kalender melalui email kepada peserta sehingga mereka dapat menambahkan rapat ke kalender mereka."; /* Start meeting tip view message bold text */ "meetings.scheduleMeeting.startMeetingTip.startMeeting"="Mulai pertemuan"; /* Recurring meeting tip view message bold text */ "meetings.scheduleMeeting.recurringMeetingTip.occurrences"="Kejadian"; /* Change SFU server alert title. */ -"settings.about.sfu.changeAlert.title"="Change SFU server"; +"settings.about.sfu.changeAlert.title"="Ubah server SFU"; /* Change SFU server alert message. */ -"settings.about.sfu.changeAlert.message"="Default is -1"; +"settings.about.sfu.changeAlert.message"="Standarnya adalah -1"; /* Change SFU server alert test field placeholder. */ -"settings.about.sfu.changeAlert.placeholder"="Enter SFU ID"; +"settings.about.sfu.changeAlert.placeholder"="Masukan ID SFU"; /* Change SFU server alert cancel button. */ "settings.about.sfu.changeAlert.cancelButton"="Batal"; /* Change SFU server alert change button. */ "settings.about.sfu.changeAlert.changeButton"="Ganti"; /* Back button menu item title visible when navigating back to 1:1 chat . */ -"chat.backButton.oneToOne.menu"="Chat with %@"; +"chat.backButton.oneToOne.menu"="Obrolan dengan %@"; /* Schedule meeting waiting room setting title */ "meetings.scheduleMeeting.waitingRoom"="Ruang tunggu"; /* Schedule meeting waiting room setting description */ -"meetings.scheduleMeeting.waitingRoom.description"="Only users admitted by the host can join the meeting."; +"meetings.scheduleMeeting.waitingRoom.description"="Hanya pengguna yang diakui oleh penyelenggara yang dapat bergabung dalam rapat."; /* Schedule meeting waiting room warning banner title */ -"meetings.scheduleMeeting.waitingRoomWarningBanner.title"="Participants added by non-hosts during calls won’t be sent to the waiting room."; +"meetings.scheduleMeeting.waitingRoomWarningBanner.title"="Peserta yang ditambahkan oleh non-host selama panggilan tidak akan dikirim ke ruang tunggu."; /* Schedule meeting waiting room warning banner learn more */ "meetings.scheduleMeeting.waitingRoomWarningBanner.learnMore"="Belajar lagi"; /* Album saved to cloud drive alert message */ -"albumLink.alert.message.albumSavedToCloudDrive"="“%@” saved to Cloud drive"; +"albumLink.alert.message.albumSavedToCloudDrive"="“%@” disimpan ke drive Cloud"; /* Album failed to save to cloud drive alert message */ -"albumLink.alert.message.albumFailedToSaveToCloudDrive"="“%@” can’t be saved to Cloud drive. Try again later and if the problem continues, contact the person who shared the link with you."; +"albumLink.alert.message.albumFailedToSaveToCloudDrive"="“%@” tidak dapat disimpan ke drive Cloud. Coba lagi nanti dan jika masalah berlanjut, hubungi orang yang membagikan tautan tersebut kepada anda."; /* Text displayed on banner we show when sharing folder with unverified contacts */ -"shareFolder.contactsNotVerified"="Some of the contacts you’re sharing information with haven’t been approved by you. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"shareFolder.contactsNotVerified"="Beberapa kontak yang anda bagikan informasi belum anda setujui. Untuk memastikan keamanan ekstra, kami menyarankan anda menyetujui kredensial mereka di Kontak dengan mengetuk ⓘ di samping kontak yang ingin anda setujui."; /* Rename public album alert title */ "albumLink.alert.renameAlbum.title"="Ganti nama album"; /* Rename public album alert message */ -"albumLink.alert.renameAlbum.message"="An album named “%@” already exists in your Albums. Enter a new name for the album you’re saving."; +"albumLink.alert.renameAlbum.message"="Sebuah album bernama “%@” sudah ada di Album anda. Masukkan nama baru untuk album yang anda simpan."; /* Button title of the dialog for transfer quota error that will direct to upgrade plan */ "transferQuotaError.button.upgrade"="Tingkatkan"; /* Button title of the dialog for transfer quota error that will dismiss dialog */ @@ -3653,21 +3657,21 @@ /* Button title of the dialog for transfer quota error to buy new plan */ "transferQuotaError.button.buyNewPlan"="Beli paket baru"; /* Footer message of the dialog for transfer quota error. [A] will be replaced by the usage quota percent. [B] will be replaced by the max transfer quota in TB. For example: 100% of 16 TB used */ -"transferQuotaError.footerMessage.quotaUsage"="[A]%% of [B] TB used"; +"transferQuotaError.footerMessage.quotaUsage"="[A]%% dari [B] TB terpakai"; /* Title of the dialog for transfer quota error with limited available trasfer quota */ "transferQuotaError.downloadLimitedQuota.title"="Quota transfer tersedia terbatas"; /* Message text of the dialog for transfer quota error with limited available trasfer quota for free accounts */ -"transferQuotaError.downloadLimitedQuota.freeAccount.message"="Downloading may be interrupted as you’ve used most of your transfer quota for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota."; +"transferQuotaError.downloadLimitedQuota.freeAccount.message"="Pengunduhan mungkin terhenti karena anda telah menggunakan sebagian besar kuota transfer untuk alamat IP ini. Untuk mendapatkan lebih banyak kuota, upgrade ke paket Pro atau tunggu %@ hingga lebih banyak kuota gratis tersedia di alamat IP anda. [A]Pelajari selengkapnya[/A] tentang kuota transfer."; /* Message text of the dialog for transfer quota error with limited available trasfer quota for pro accounts */ "transferQuotaError.downloadLimitedQuota.proAccount.message"="Pengunduhan mungkin terhenti karena anda telah menggunakan sebagian besar kuota transfer di akun ini. Untuk mendapatkan lebih banyak kuota, beli paket baru, atau jika anda memiliki langganan berulang dengan MEGA, anda dapat menunggu perpanjangan paket."; /* Title of the dialog for transfer quota error with exceeded quota */ "transferQuotaError.downloadExceededQuota.title"="Kuota transfer telah terlewati"; /* Message text of the dialog for transfer quota error with exceeded download trasfer quota for free accounts */ -"transferQuotaError.downloadExceededQuota.freeAccount.message"="You can’t continue downloading as you don’t have any transfer quota left for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota."; +"transferQuotaError.downloadExceededQuota.freeAccount.message"="Anda tidak dapat melanjutkan pengunduhan karena tidak ada sisa kuota transfer untuk alamat IP ini. Untuk mendapatkan lebih banyak kuota, upgrade ke paket Pro atau tunggu %@ hingga lebih banyak kuota gratis tersedia di alamat IP anda. [A]Pelajari selengkapnya[/A] tentang kuota transfer."; /* Message text of the dialog for transfer quota error with exceeded download trasfer quota for pro accounts */ "transferQuotaError.downloadExceededQuota.proAccount.message"="Anda tidak dapat melanjutkan pengunduhan karena anda tidak memiliki sisa kuota transfer di akun ini. Untuk mendapatkan lebih banyak kuota, beli paket baru, atau jika anda memiliki langganan berulang dengan MEGA, anda dapat menunggu perpanjangan paket."; /* Message text of the dialog for transfer quota error with exceeded streaming trasfer quota for free accounts */ -"transferQuotaError.streamingExceededQuota.freeAccount.message"="Your media stopped playing as you don’t have any transfer quota left for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota."; +"transferQuotaError.streamingExceededQuota.freeAccount.message"="Media anda berhenti diputar karena anda tidak memiliki sisa kuota transfer untuk alamat IP ini. Untuk mendapatkan lebih banyak kuota, upgrade ke paket Pro atau tunggu %@ hingga lebih banyak kuota gratis tersedia di alamat IP anda. [A]Pelajari selengkapnya[/A] tentang kuota transfer."; /* Message text of the dialog for transfer quota error with exceeded download trasfer quota for pro accounts */ "transferQuotaError.streamingExceededQuota.proAccount.message"="Media anda berhenti diputar karena anda tidak memiliki sisa kuota transfer di akun ini. Untuk mendapatkan lebih banyak kuota, beli paket baru, atau jika anda memiliki langganan berulang dengan MEGA, anda dapat menunggu perpanjangan paket."; /* Meeting waiting room leave button */ @@ -3675,7 +3679,7 @@ /* Meeting waiting room leave alert message */ "meetings.waitingRoom.alert.leaveMeeting"="Keluar dari rapat?"; /* Meeting waiting room don't leave button */ -"meetings.waitingRoom.alert.dontLeave"="Don’t leave"; +"meetings.waitingRoom.alert.dontLeave"="Jangan pergi"; /* Meeting waiting room guest join first name textfield */ "meetings.waitingRoom.guest.firstName"="Nama depan"; /* Meeting waiting room guest join last name textfield */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Gabung"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wait for host to let you in"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Tunggu tuan rumah mengizinkan anda masuk"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Tunggu tuan rumah memulai pertemuan"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="Host didn’t let you in"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="OK, mengerti"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ sedang menunggu untuk bergabung dalam panggilan tersebut "; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Tolak"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Akui"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="Lihat ruang tunggu"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Akui semuanya"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Tolak %@ masuk?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Tolak masuk"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Batal"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ sedang menunggu untuk bergabung “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Buka di MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/id.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/id.lproj/Localizable.stringsdict similarity index 98% rename from iMEGA/Languages/id.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/id.lproj/Localizable.stringsdict index 11f2d29498..a363a59983 100644 --- a/iMEGA/Languages/id.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/id.lproj/Localizable.stringsdict @@ -26,10 +26,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - %d item removed other - %d items removed + %d item dihapus sharedItems.rubbish.confirmation.fileCount @@ -938,10 +936,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Every day effective [B][StartDate] until [UntilDate][/B] from [B][StartTime] to [EndTime][/B] other - Every %d days effective [B][StartDate] until [UntilDate][/B] from [B][StartTime] to [EndTime][/B] + Setiap %d hari efektif [B][StartDate] sampai [UntilDate][/B] dari [B][StartTime] hingga [EndTime][/B] meetings.scheduled.recurring.daily.forever @@ -954,10 +950,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Every day effective [B][StartDate][/B] from [B][StartTime] to [EndTime][/B] other - Every %d days effective [B][StartDate][/B] from [B][StartTime] to [EndTime][/B] + Setiap %d hari efektif [B][StartDate][/B] dari [B][StartTime] hingga [EndTime][/B] meetings.scheduled.recurring.weekly.oneDay.until @@ -2258,10 +2252,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Meeting will occur every month on day [cardinalDay]. other - Meeting will occur every %d months on day [cardinalDay]. + Pertemuan akan terjadi setiap %d bulan pada hari [cardinalDay]. meetings.scheduled.create.monthly.multipleDaysCardinal.footerNote @@ -2274,10 +2266,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Meeting will occur every month on day [cardinalDays] and day [cardinalLastDay]. other - Meeting will occur every %d months on day [cardinalDays] and day [cardinalLastDay]. + Pertemuan akan terjadi setiap %d bulan pada hari [cardinalDays] dan hari [cardinalLastDay]. meetings.scheduled.create.monthly.weekNumberAndWeekDay.footerNote @@ -2332,10 +2322,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Every month on day [cardinalNumber] other - Every %d months on day [cardinalNumber] + Setiap %d bulan pada hari [cardinalNumber] meetings.scheduled.create.monthly.weekNumberAndWeekDay.selectedFrequency @@ -2367,6 +2355,34 @@ albumLink.alert.message.filesSaveToCloudDrive + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + %d file disimpan ke drive Cloud + + + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + %d pengunduhan dimulai + + + general.format.itemsSelected NSStringLocalizedFormatKey %#@count@ @@ -2377,9 +2393,9 @@ NSStringFormatValueTypeKey d one - %d file saved to Cloud drive + %d item selected other - %d files saved to Cloud drive + %d items selected diff --git a/iMEGA/Languages/it.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/it.lproj/Localizable.strings similarity index 99% rename from iMEGA/Languages/it.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/it.lproj/Localizable.strings index fd506e3716..2a0be77e6b 100644 --- a/iMEGA/Languages/it.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/it.lproj/Localizable.strings @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="In precedenza, hai gà sottoscritto un piano Pro attraverso Google Play o Huawei AppGallery. Per favore, annulla i tuoi abbonamenti direttamente tramite queste applicazioni sul tuo dispositivo e riprova."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Vuoi annullare il tuo abbonamento corrente e continuare con l'acquisto?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Not available with your current plan"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Abbonamento attivo"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Aggiorna"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="Il file verrà aggiornato con una nuova versione."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Applica a tutti i %@ duplicati"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="Ci sei solo tu qui"; /* Meetings end call dialog description */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Bloccato"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Dispositivo sconosciuto"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Mostra nel Cloud drive"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="Termina evento"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Unisciti"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wait for host to let you in"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wait for host to let you in"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Wait for host to start the meeting"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="Host didn’t let you in"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="OK, capito"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ is waiting to join the call"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Nega"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Admit"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="See waiting room"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Admit all"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Deny %@ entry?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Deny entry"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Annulla"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ is waiting to join “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Open in MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/it.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/it.lproj/Localizable.stringsdict similarity index 99% rename from iMEGA/Languages/it.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/it.lproj/Localizable.stringsdict index 99c0067d8a..b352ac0583 100644 --- a/iMEGA/Languages/it.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/it.lproj/Localizable.stringsdict @@ -3032,5 +3032,37 @@ %d files saved to Cloud drive + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Download started + other + %d downloads started + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/iMEGA/Languages/ja.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ja.lproj/Localizable.strings similarity index 99% rename from iMEGA/Languages/ja.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ja.lproj/Localizable.strings index 9d8b488518..d04c142e20 100644 --- a/iMEGA/Languages/ja.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ja.lproj/Localizable.strings @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="以前にGoogle PlayまたはAppGalleryでProプランにご加入されたことがございます。お使いの端末のGoogle PlayまたはHuawei AppGallery内で、手動でサブスクリプションをキャンセルしてから再試行してください。"; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="現在のサブスクリプションをキャンセルして、ご購入を継続されますか?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="お客様の現在のプランではご利用いただけません"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="アクティブサブスクリプション"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="更新"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="ファイルは新しいバージョンに更新されます。"; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="%@個の重複すべてに適用"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="ここにいるのはあなただけです"; /* Meetings end call dialog description */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="ブロックされています"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="端末が不明です"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="クラウドドライブに表示"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="バックアップに表示"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="定期開催を終了する"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="参加する"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="主催者が参加を許可するまでお待ちください"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="主催者が参加を許可するまでお待ちください"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="主催者がミーティングを開始するまでお待ちください"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="主催者があなたを参加させませんでした"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="待合室から退出させられます"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="はい、わかりました。"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@さんが通話への参加を待っています"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="拒否する"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="受け入れる"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="待合室を見る"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="すべて受け入れる"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="%@さんの入室を拒否しますか?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="入室を拒否"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="キャンセル"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@さんが「%@」への参加を待っています"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="MEGAで開く"; \ No newline at end of file diff --git a/iMEGA/Languages/ja.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ja.lproj/Localizable.stringsdict similarity index 98% rename from iMEGA/Languages/ja.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ja.lproj/Localizable.stringsdict index 4ef4d45409..a324133cbf 100644 --- a/iMEGA/Languages/ja.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ja.lproj/Localizable.stringsdict @@ -2368,5 +2368,33 @@ %d個のファイルがクラウドドライブに保存されました + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + %d件のダウンロードが開始されました + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + %d項目が選択されました + + \ No newline at end of file diff --git a/iMEGA/Languages/ko.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ko.lproj/Localizable.strings similarity index 99% rename from iMEGA/Languages/ko.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ko.lproj/Localizable.strings index d578885f0f..34fe0d53c3 100644 --- a/iMEGA/Languages/ko.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ko.lproj/Localizable.strings @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="이전에 Google Play 또는 AppGallery를 통하여 구독을 하였습니다. 기기에서 Google Play 또는 Huawei AppGallery 안에서 구독을 수동으로 취소한 후 다시 시도하세요."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="현재 구독을 취소하고 결제를 진행하시겠습니까?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="현재 요금제에서는 이용이 불가합니다"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="활성 구독"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="업데이트"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="파일이 새 버전으로 업데이트 됩니다."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="모든 %@개의 중복에 적용"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="여기에 당신만 있습니다"; /* Meetings end call dialog description */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="차단됨"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="알 수 없는 장치"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="클라우드 드라이브에서 보기"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="되풀이 종료"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="참여"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="주최자가 당신을 들어오게 하기를 기다립니다"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="주최자가 당신을 들어오게 하기를 기다립니다"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="주최자가 회의를 시작하기를 기다립니다"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="주최자가 당신을 들어오게 하지 않았습니다"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="알겠습니다"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ is waiting to join the call"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="거부"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="허용"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="See waiting room"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="모두 허용"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Deny %@ entry?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Deny entry"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="취소"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ is waiting to join “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Open in MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/ko.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ko.lproj/Localizable.stringsdict similarity index 98% rename from iMEGA/Languages/ko.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ko.lproj/Localizable.stringsdict index 2ac285a08c..ec52027ec0 100644 --- a/iMEGA/Languages/ko.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ko.lproj/Localizable.stringsdict @@ -2368,5 +2368,37 @@ %d개의 파일이 클라우드 드라이브에 저장되었습니다. + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Download started + other + %d downloads started + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/iMEGA/Languages/nl.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/nl.lproj/Localizable.strings similarity index 99% rename from iMEGA/Languages/nl.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/nl.lproj/Localizable.strings index 3a72ebbdcc..2402243234 100644 --- a/iMEGA/Languages/nl.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/nl.lproj/Localizable.strings @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="U heeft zich eerder geabonneerd op een Pro-abonnement met Google Play of Applicatie Gallerij. Annuleer uw abonnement handmatig bij hen in Google Play of de Huawei Applicatie Gallerij op uw apparaat en probeer het opnieuw."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Wilt u uw huidige abonnement opzeggen en doorgaan met de aankoop?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Not available with your current plan"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Actief Abonnement"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Update"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="Het bestand wordt bijgewerkt met een nieuwe versie."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Toepassen op alle %@ duplicaten"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="U bent de enige hier"; /* Meetings end call dialog description */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Geblokkeerd"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Onbekend apparaat"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Weergeven in Cloud Schijf"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Weergeven in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="Herhaling beëindigen"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Aansluiten"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wacht tot de host u binnenlaat"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wacht tot de host u binnenlaat"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Wacht tot de host de vergadering start"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="De host heeft u niet binnengelaten"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="OK, ik snap het"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ wacht om deel te nemen aan het gesprek"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Afwijzen"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Toelaten"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="Zie wachtkamer"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Iedereen toelaten"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="%@ de toegang weigeren?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Toegang weigeren"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Annuleren"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ wacht om lid te worden van de “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Openen in MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/nl.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/nl.lproj/Localizable.stringsdict similarity index 99% rename from iMEGA/Languages/nl.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/nl.lproj/Localizable.stringsdict index 449556295d..d1edef02d1 100644 --- a/iMEGA/Languages/nl.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/nl.lproj/Localizable.stringsdict @@ -2706,5 +2706,37 @@ %d bestanden opgeslagen naar Cloud schijf + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Download gestart + other + %d downloads gestart + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item geselecteerd. + other + %d items geselecteerd + + \ No newline at end of file diff --git a/iMEGA/Languages/pl.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pl.lproj/Localizable.strings similarity index 99% rename from iMEGA/Languages/pl.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pl.lproj/Localizable.strings index 93bab12e15..3da155c876 100644 --- a/iMEGA/Languages/pl.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pl.lproj/Localizable.strings @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="Wcześniej subskrybowałeś plan Pro w Google Play lub AppGallery. Prosimy o ręczne anulowanie subskrypcji w Google Play lub Huawei AppGallery na swoim urządzeniu, a następnie ponowienie próby."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Czy chcesz anulować swoją obecną subskrypcję i kontynuować zakup?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Niedostępne w ramach bieżącego planu"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Aktywna subskrypcja"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Zapisz zmiany"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="Plik zostanie zaktualizowany o nową wersję."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Zastosuj do wszystkich %@ duplikatów"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="Tylko ty tu jesteś"; /* Meetings end call dialog description */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Zablokowane"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Nieznane urządzenie"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Pokaż na dysku w chmurze"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="Zakończ powtarzanie"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Dołącz"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Poczekaj na wpuszczenie przez hosta"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Poczekaj na wpuszczenie przez hosta"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Oczekiwanie na rozpoczęcie spotkania przez gospodarza"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="Gospodarz cię nie wpuścił"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="OK, rozumiem"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ oczekuje na dołączenie do połączenia"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Anulowane"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Zezwól"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="Zobacz poczekalnię"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Zezwól wszystkim"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Odmówić wstępu dla %@?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Odmowa wstępu"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Anuluj"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ oczekuje na dołączenie do \"%@\""; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Otwórz w MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/pl.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pl.lproj/Localizable.stringsdict similarity index 99% rename from iMEGA/Languages/pl.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pl.lproj/Localizable.stringsdict index 3546a16cd0..f463213a33 100644 --- a/iMEGA/Languages/pl.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pl.lproj/Localizable.stringsdict @@ -3382,5 +3382,41 @@ %d plików zapisano na Dysku w chmurze + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Pobieranie rozpoczęte + few + Rozpoczęto %d pobierania + many + Rozpoczęto %d pobierania + other + Rozpoczęto %d pobierania + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/iMEGA/Languages/pt.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pt.lproj/Localizable.strings similarity index 99% rename from iMEGA/Languages/pt.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pt.lproj/Localizable.strings index 1ee1dc3be8..39af3f3a55 100644 --- a/iMEGA/Languages/pt.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pt.lproj/Localizable.strings @@ -2399,7 +2399,7 @@ /* Text to indicate the user to reset/change the password of a link */ "Reset Password"="Resetar senha"; /* Stand-alone error message shown to users who attempt to load/access a link where the user has been suspended/taken-down due to severe violation of our terms of service. */ -"This link is unavailable as the user’s account has been closed for gross violation of MEGA’s [A]Terms of Service[/A]."="Este link não está disponível porque a conta do usuário que o criou foi encerrada devido a uma grave violação dos [A]Termos de serviço[/A] do MEGA."; +"This link is unavailable as the user’s account has been closed for gross violation of MEGA’s [A]Terms of Service[/A]."="Este link não está disponível porque a conta do usuário que o criou foi encerrada devido a uma grave infração dos [A]Termos de serviço[/A] do MEGA."; /* Stand-alone error message shown to users who attempt to load/access a link where the link has been taken down due to severe violation of our terms of service. */ "Taken down due to severe violation of our terms of service"="Este arquivo ou pasta foi denunciado por armazenar conteúdo censurável, como material relacionado à exploração infantil, extremismo violento ou bestialidade. A conta do usuário que criou este link foi encerrada e as informações desta conta, incluindo o endereço IP, foram proporcionadas às autoridades competentes."; /* Text used to show the user that some resource is not available */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="Você já tem uma assinatura de um plano Pro por meio da Google Play Store ou da AppGallery. Por favor, cancele a sua assinatura diretamente na Google Play Store ou na Huawei AppGallery no seu dispositivo, e então tente novamente fazer a nova compra."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Você quer cancelar a sua assinatura atual e continuar com a nova compra?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Não compatível com o seu plano atual"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Assinatura Ativa"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Atualizar"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="O arquivo será atualizado com uma nova versão."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Aplicar a todos os %@ duplicados"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="Não tem mais ninguém aqui"; /* Meetings end call dialog description */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Bloqueado"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Dispositivo desconhecido"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Mostrar na Nuvem de arquivos"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Mostrar nos Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="Deixar de repetir"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Entrar"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Espere até que o host deixe você entrar"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Espere até que o host deixe você entrar"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Espere até que o host inicie a reunião"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="O anfitrião não deixou você entrar"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="Você será removido da sala de espera"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="Entendi"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ está esperando para entrar na chamada"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Rejeitar"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Aceitar"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="Ver sala de espera"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Aceitar a todos"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Não permitir o acesso de %@?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Não permitir o acesso"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Cancelar"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ está esperando para entrar em “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Abrir no MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/pt.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pt.lproj/Localizable.stringsdict similarity index 99% rename from iMEGA/Languages/pt.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pt.lproj/Localizable.stringsdict index 87f8604f18..55f9a51583 100644 --- a/iMEGA/Languages/pt.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/pt.lproj/Localizable.stringsdict @@ -3044,5 +3044,41 @@ %d arquivos foram salvos na Nuvem de arquivos + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Download iniciado + many + %d de downloads foram iniciados + other + %d downloads foram iniciados + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selecionado + many + %d de itens selecionados + other + %d itens selecionados + + \ No newline at end of file diff --git a/iMEGA/Languages/ro.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ro.lproj/Localizable.strings similarity index 98% rename from iMEGA/Languages/ro.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ro.lproj/Localizable.strings index 9238700076..6f81962bc7 100644 --- a/iMEGA/Languages/ro.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ro.lproj/Localizable.strings @@ -211,7 +211,7 @@ /* Title shown when a folder doesn't have any files */ "emptyFolder"="Folder gol"; /* Title shown when your Cloud Drive is empty, when you don't have any files. */ -"cloudDriveEmptyState_title"="Niciun fișier în unitatea cloud"; +"cloudDriveEmptyState_title"="Niciun fișier în Unitatea cloud"; /* Button title shown in empty views when you can 'Add files' */ "addFiles"="Adaugă fișiere"; /* Title shown when you make a search and there is 'No Results' */ @@ -353,7 +353,7 @@ /* A tooltip message which shows when a file name is duplicated during renaming. */ "There is already a file with the same name"="Există deja un fișier cu același nume"; /* Browser save to cloud drive select button title */ -"cloudDrive.browser.saveToCloudDrive.title"="Salvează în unitatea cloud"; +"cloudDrive.browser.saveToCloudDrive.title"="Salvează în Unitatea cloud"; /* List option shown on the details of a file or folder after the user has copied it */ "cloudDrive.browser.paste"="Lipește"; /* Tapping on this button open Files.app and allow users select files to upload them to MEGA */ @@ -529,7 +529,7 @@ /* Button title that skips the current action */ "skipButton"="Omite"; /* Text shown to explain what means 'Enable Camera Uploads'. The 'Cloud Drive' is the MEGA section, so please keep consistency */ -"Automatically backup your photos and videos to the Cloud Drive."="Fă backup automat fotografiilor și videoclipurilor în unitatea cloud."; +"Automatically backup your photos and videos to the Cloud Drive."="Fă backup automat fotografiilor și videoclipurilor în Unitatea cloud."; /* Button title that enables the functionality 'Camera Uploads', which uploads all the photos in your device to MEGA */ "enableCameraUploadsButton"="Activează Încărcări cameră"; /* Success message shown when Camera Uploads has been enabled */ @@ -1249,7 +1249,7 @@ /* 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"="[A] a părăsit chatul de grup"; /* A log message in a chat conversation to tell the reader that a participant [A] was added to the chat by a moderator [B]. Please keep the [A] and [B] placeholders, they will be replaced by the participant and the moderator names at runtime. For example: Alice joined the group chat by invitation from Frank. */ -"joinedTheGroupChatByInvitationFrom"="[A] s-a alăturat chatului de grup prin invitație de la [B]."; +"joinedTheGroupChatByInvitationFrom"="[A] s-a alăturat chatului de grup la invitația [B]."; /* A log message in the chat conversation to tell the reader that a participant [A] cleared the history of the chat. For example, Alice cleared the chat history. */ "clearedTheChatHistory"="[A] a golit istoricul chatului."; /* 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'. */ @@ -1375,7 +1375,7 @@ /* Button which allows to delete message in chat conversation. */ "deleteMessage"="Șterge mesajul"; /* */ -"fromCloudDrive"="Din unitatea cloud"; +"fromCloudDrive"="Din Unitatea cloud"; /* A button label. The button sends contact information to a user in the conversation. */ "sendContact"="Trimite contacte"; /* */ @@ -1687,7 +1687,7 @@ /* Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device */ "youNeedATwoFactorAuthenticationApp"="Ne pare rău, autentificarea cu doi factori nu poate fi activată pe dispozitiv. Te rugăm să deschizi App Store pentru a instala o aplicație de autentificare."; /* 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."="Ai nevoie de o aplicație de autentificare pentru a activa 2FA pe MEGA. Poți descărca și instala aplicația Google Authenticator, Duo Mobile, Authy sau Microsoft Authenticator pentru telefon sau 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."="Ai nevoie de o aplicație de autentificare pentru a activa A2F pe MEGA. Poți descărca și instala aplicația Google Authenticator, Duo Mobile, Authy sau Microsoft Authenticator pentru telefon sau tabletă."; /* A title on the mobile web client page showing that 2FA has been enabled successfully. */ "twoFactorAuthenticationEnabled"="Autentificarea cu doi factori activată"; /* A message on the dialog shown after 2FA was successfully enabled. */ @@ -1695,7 +1695,7 @@ /* A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. */ "pleaseSaveYourRecoveryKey"="Te rugăm să salvezi cheia de recuperare într-o locație sigură"; /* An informational message on the Backup Recovery Key dialog. */ -"twoFactorAuthenticationEnabledWarning"="Dacă pierzi accesul la contul tău după ce ai activat 2FA și nu ai făcut un backup al cheii de recuperare, MEGA nu te poate ajuta să obții din nou accesul la acesta."; +"twoFactorAuthenticationEnabledWarning"="Dacă pierzi accesul la contul tău după ce ai activat A2F și nu ai făcut un backup al cheii de recuperare, MEGA nu te poate ajuta să obții din nou accesul la acesta."; /* A message on a dialog to say that 2FA has been successfully disabled. */ "twoFactorAuthenticationDisabled"="Autentificarea cu doi factori dezactivată"; /* A message on the setup two-factor authentication page on the mobile web client. */ @@ -1723,7 +1723,7 @@ /* Title shown in a page of the on boarding screens explaining that the chat is encrypted */ "Encrypted chat"="Chat criptat"; /* 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."="Chat complet criptat cu apeluri vocale și video, mesagerie de grup și integrare pentru partajarea de fișiere din unitatea cloud."; +"Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud Drive."="Chat complet criptat cu apeluri vocale și video, mesagerie de grup și integrare pentru partajarea de fișiere din Unitatea cloud."; /* Title shown in a page of the on boarding screens explaining that the user can add contacts to chat and colaborate */ "Create your Network"="Creează-ți rețeaua"; /* Description shown in a page of the onboarding screens explaining contacts */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="V-ați abonat anterior la un plan Pro cu Google Play sau AppGallery. Vă rugăm să anulați manual abonamentul cu acestea în Google Play sau Huawei AppGallery de pe dispozitiv și apoi încercați din nou."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Doriți să anulați abonamentul curent și să continuați cu achiziția?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Nu este disponibil cu planul dvs. actual"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Abonament activ"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -2931,7 +2933,7 @@ /* No backups and old inbox files in the account message */ "backups.empty.state.message"="Niciun element în backupuri"; /* No backups and old inbox files in the account description message */ -"backups.empty.state.description"="Aici sunt stocate fișierele și folderele pentru care s-a făcut backup. Elementele pentru care s-a făcut backup sunt de tip „numai citire” pentru a le proteja împotriva modificării accidentale în unitatea cloud.\nPoți face backupuri ale elementelor de pe calculator pe MEGA folosind aplicația noastră desktop."; +"backups.empty.state.description"="Aici sunt stocate fișierele și folderele pentru care s-a făcut backup. Elementele pentru care s-a făcut backup sunt de tip „numai citire” pentru a le proteja împotriva modificării accidentale în Unitatea cloud.\nPoți face backupuri ale elementelor de pe calculator pe MEGA folosind aplicația noastră desktop."; /* Title of the alert shown when you confirm to cancel pending transfers */ "transfers.cancellable.title"="Anulezi transferurile?"; /* Message for action of cancelling a transfer */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Actualizează"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="Fișierul va fi actualizat cu o nouă versiune."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Aplică la toate cele %@ de duplicate"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="Ești singura persoană de aici"; /* Meetings end call dialog description */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Blocat"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Dispozitiv necunoscut"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Afișează în Unitatea cloud"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Afișează în Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="Încheiați recurența"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3641,7 +3645,7 @@ /* Album failed to save to cloud drive alert message */ "albumLink.alert.message.albumFailedToSaveToCloudDrive"="„%@” nu poate fi salvat pe Unitatea cloud. Încercați din nou mai târziu și dacă problema continuă, contactați persoana care a partajat linkul cu dvs."; /* Text displayed on banner we show when sharing folder with unverified contacts */ -"shareFolder.contactsNotVerified"="Unele persoane de contact cu care partajați informații nu au fost aprobate de dvs. Pentru a asigura un plus de securitate, vă recomandăm să aprobați acreditările lor în Contacte apăsând pe ⓘ lângă contactul pe care doriți să îl aprobați."; +"shareFolder.contactsNotVerified"="Unele contacte cu care partajați informații nu au fost aprobate de dvs. Pentru a asigura un plus de securitate, vă recomandăm să aprobați acreditările lor în Contacte apăsând pe ⓘ lângă contactul pe care doriți să îl aprobați."; /* Rename public album alert title */ "albumLink.alert.renameAlbum.title"="Redenumește albumul"; /* Rename public album alert message */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Alătură-te"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Așteptați ca gazda să vă lase să intrați"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Așteptați ca gazda să vă lase să intrați"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Așteptați ca gazda să înceapă întâlnirea"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="Gazda nu v-a lăsat să intre"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="Veți părăsi sala de așteptare"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="OK, am înțeles"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ așteaptă să se alăture apelului"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Refuză"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Admite"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="Vezi sala de așteptare"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Admite toate"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Să-i refuzi intrarea %@?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Refuzați intrarea"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Anulează"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ așteaptă să se alăture la „%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Deschide în MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/ro.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ro.lproj/Localizable.stringsdict similarity index 95% rename from iMEGA/Languages/ro.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ro.lproj/Localizable.stringsdict index 2b9f4fd626..dfd17eac89 100644 --- a/iMEGA/Languages/ro.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ro.lproj/Localizable.stringsdict @@ -2731,9 +2731,11 @@ NSStringFormatValueTypeKey d one - Sent [A] to chat + A trimis [A] la chat + few + A trimis [A] la toate chat-urile other - Sent [A] to all chats + A trimis [A] la toate chat-urile meetings.scheduled.create.daily.interval @@ -2801,9 +2803,11 @@ NSStringFormatValueTypeKey d one - Meeting will occur every day. + Întâlnirile vor avea loc în fiecare zi. + few + Întâlnirea va avea loc la fiecare %d zile. other - Meeting will occur every %d days. + Întâlnirea va avea loc la fiecare %d zile. meetings.scheduled.create.weekly.singleDay.footerNote @@ -2817,9 +2821,11 @@ NSStringFormatValueTypeKey d one - Meeting will occur every week on [weekDayName]. + Întâlnirea va avea loc în fiecare săptămână, [weekDayName]. + few + Întâlnirea va avea loc la fiecare %d săptămâni, [weekDayName] other - Meeting will occur every %d weeks on [weekDayName]. + Întâlnirea va avea loc la fiecare %d săptămâni, [weekDayName] meetings.scheduled.create.weekly.multipleDays.footerNote @@ -2833,9 +2839,11 @@ NSStringFormatValueTypeKey d one - Meeting will occur every week on [weekDayNames] and [lastWeekDayName]. + Întâlnirea va avea loc în fiecare săptămână, [weekDayNames] și [lastWeekDayName]. + few + Întâlnirea va avea loc la fiecare %d săptămâni, [weekDayNames] și [lastWeekDayName]. other - Meeting will occur every %d weeks on [weekDayNames] and [lastWeekDayName]. + Întâlnirea va avea loc la fiecare %d săptămâni, [weekDayNames] și [lastWeekDayName]. meetings.scheduled.create.monthly.singleDay.footerNote @@ -2849,9 +2857,11 @@ NSStringFormatValueTypeKey d one - Meeting will occur every month on the [ordinalDay]. + Întâlnirea va avea loc în fiecare lună în zilele [ordinalDay]. + few + Întâlnirea va avea loc la fiecare %d luni în zilele [ordinalDay]. other - Meeting will occur every %d months on the [ordinalDay]. + Întâlnirea va avea loc la fiecare %d luni în zilele [ordinalDay]. meetings.scheduled.create.monthly.multipleDays.footerNote @@ -2865,9 +2875,11 @@ NSStringFormatValueTypeKey d one - Meeting will occur every month on the [ordinalDays] and [ordinalLastDay]. + Întâlnirea va avea loc în fiecare lună în zilele [ordinalDays] și [ordinalLastDay]. + few + Întâlnirea va avea loc la fiecare %d luni în zilele [ordinalDays] și [ordinalLastDay]. other - Meeting will occur every %d months on the [ordinalDays] and [ordinalLastDay]. + Întâlnirea va avea loc la fiecare %d luni în zilele [ordinalDays] și [ordinalLastDay]. meetings.scheduled.create.monthly.singleDayCardinal.footerNote @@ -2881,9 +2893,11 @@ NSStringFormatValueTypeKey d one - Meeting will occur every month on day [cardinalDay]. + Întâlnirea va avea loc în fiecare lună în ziua [cardinalDay]. + few + Întâlnirea va avea loc la fiecare %d luni în ziua [cardinalDay]. other - Meeting will occur every %d months on day [cardinalDay]. + Întâlnirea va avea loc la fiecare %d luni în ziua [cardinalDay]. meetings.scheduled.create.monthly.multipleDaysCardinal.footerNote @@ -2897,9 +2911,11 @@ NSStringFormatValueTypeKey d one - Meeting will occur every month on day [cardinalDays] and day [cardinalLastDay]. + Întâlnirea va avea loc în fiecare lună în ziua [cardinalDays] și în ziua [cardinalLastDay]. + few + Întâlnirea va avea loc la fiecare %d luni în ziua [cardinalDays] și în ziua [cardinalLastDay]. other - Meeting will occur every %d months on day [cardinalDays] and day [cardinalLastDay]. + Întâlnirea va avea loc la fiecare %d luni în ziua [cardinalDays] și în ziua [cardinalLastDay]. meetings.scheduled.create.monthly.weekNumberAndWeekDay.footerNote @@ -2913,9 +2929,11 @@ NSStringFormatValueTypeKey d one - Meeting will occur every month on the [weekNumber] [weekDayName]. + Întâlnirea va avea loc în fiecare lună, în [weekNumber] [weekDayName]. + few + Întâlnirea va avea loc la fiecare %d luni, în [weekNumber] [weekDayName]. other - Meeting will occur every %d months on the [weekNumber] [weekDayName]. + Întâlnirea va avea loc la fiecare %d luni, în [weekNumber] [weekDayName]. meetings.scheduled.create.weekly.selectedFrequency @@ -2929,9 +2947,11 @@ NSStringFormatValueTypeKey d one - Every week on [weekDayNames] + În fiecare săptămână, [weekDayNames] + few + La fiecare %d săptămâni, [weekDayNames] other - Every %d weeks on [weekDayNames] + La fiecare %d săptămâni, [weekDayNames] meetings.scheduled.create.monthly.weekDay.selectedFrequency @@ -2945,9 +2965,11 @@ NSStringFormatValueTypeKey d one - Every month on the [ordinalNumber] + În fiecare lună pe [ordinalNumber] + few + La fiecare %d luni pe [ordinalNumber] other - Every %d months on the [ordinalNumber] + La fiecare %d luni pe [ordinalNumber] meetings.scheduled.create.monthly.weekDayCardinal.selectedFrequency @@ -2961,9 +2983,11 @@ NSStringFormatValueTypeKey d one - Every month on day [cardinalNumber] + În fiecare lună, în ziua [cardinalNumber] + few + La fiecare %d luni, în ziua [cardinalNumber] other - Every %d months on day [cardinalNumber] + La fiecare %d luni, în ziua [cardinalNumber] meetings.scheduled.create.monthly.weekNumberAndWeekDay.selectedFrequency @@ -2977,9 +3001,11 @@ NSStringFormatValueTypeKey d one - Every month, [ordinalNumber] [weekDayName] + În fiecare lună, în [ordinalNumber][weekDayName] + few + La fiecare %d luni, în [ordinalNumber] [weekDayName] other - Every %d months, [ordinalNumber] [weekDayName] + La fiecare %d luni, în [ordinalNumber] [weekDayName] meetings.scheduleMeeting.create.selectedRecurrence.daily.customInterval @@ -2993,9 +3019,11 @@ NSStringFormatValueTypeKey d one - Daily + Zilnică + few + Fiecare %d zile other - Every %d days + Fiecare %d zile albumLink.alert.message.filesSaveToCloudDrive @@ -3009,9 +3037,47 @@ NSStringFormatValueTypeKey d one - %d file saved to Cloud drive + %d fișier a fost salvat pe Unitatea cloud + few + %d fișiere au fost salvate pe Unitatea cloud + other + %d de fișiere au fost salvate pe Unitatea cloud + + + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Descărcarea a început + few + %d descărcări au început + other + %d de descărcări au început + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d element selectat + few + %d elemente selectate other - %d files saved to Cloud drive + %d de elemente selectate diff --git a/iMEGA/Languages/ru.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ru.lproj/Localizable.strings similarity index 98% rename from iMEGA/Languages/ru.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ru.lproj/Localizable.strings index 5cb68fa88d..30337bf7f3 100644 --- a/iMEGA/Languages/ru.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ru.lproj/Localizable.strings @@ -85,7 +85,7 @@ /* Message for login option on Create Account screen */ "account.createAccount.alreadyHaveAnAccount"="Уже есть аккаунт?"; /* Button title which triggers the action to create a MEGA account */ -"createAccount"="Регистрация"; +"createAccount"="Зарегистрироваться"; /* Message on login screen for Create Account */ "account.login.newToMega"="Впервые в MEGA?"; /* Button title which triggers the action to join meeting as Guest */ @@ -99,7 +99,7 @@ /* Error message when trying to login and the account is blocked */ "accountBlocked"="Ваш аккаунт был заблокирован из-за нарушения условий использования MEGA, включая, помимо прочего, пункт 15."; /* Message shown when the user writes an invalid format in the email field */ -"emailInvalidFormat"="Введите правильный email"; +"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 */ @@ -177,7 +177,7 @@ /* */ "fileLinkUnavailableText4"="Файл был удалён пользователем."; /* Message shown when a download starts */ -"downloadStarted"="Скачавание запущено"; +"downloadStarted"="Скачивание запущено"; /* Title for the folder link view */ "folderLink"="Ссылка на папку"; /* Title for the album/collection link view */ @@ -525,7 +525,7 @@ /* Message shown when some files have been imported */ "filesImported"="Файлы импортированы"; /* Title shown on the Camera Uploads section when the edit mode is enabled. On this mode you can select photos */ -"selectTitle"="Выбрать элементы"; +"selectTitle"="Выбор элементов"; /* Button title that skips the current action */ "skipButton"="Пропустить"; /* Text shown to explain what means 'Enable Camera Uploads'. The 'Cloud Drive' is the MEGA section, so please keep consistency */ @@ -635,7 +635,7 @@ /* Subtitle shown on the Contacts section under the name of the contact you have shared {Number of folders} with */ "foldersShared"="Папок: %d"; /* Item menu option to add a contact writting his/her email */ -"addFromEmail"="Ввести email"; +"addFromEmail"="Ввести адрес электронной почты"; /* Title of Shared Items section */ "sharedItems"="Общие элементы"; /* Title of the tab bar item for the Shared Items section */ @@ -1325,7 +1325,7 @@ /* A button label. The button allows the user to leave the group conversation. */ "leave"="Выйти"; /* Top menu option which opens more menu options in a context menu. */ -"more"="Больше"; +"more"="Ещё"; /* A button label. The button allows the user to mute a conversation. */ "mute"="Приглушить"; /* A button label. The button allows the user to unmute a conversation. */ @@ -2043,7 +2043,7 @@ /* Text showing the user how many contacts would be invited */ "Invite [X] contacts"="Пригласить контакты ([X])"; /* Text showing the user how to write more than one email in order to invite them to MEGA */ -"Tap space to enter multiple emails"="Нажмите пробел, чтобы ввести несколько электронных писем"; +"Tap space to enter multiple emails"="Нажмите пробел, чтобы ввести несколько адресов"; /* Text used as a section title or similar */ "Enter Email"="Введите email"; /* Text to send as SMS message to user contacts inviting them to MEGA */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="Вы ранее подписались на план Pro в Google Play или AppGallery. Пожалуйста, вручную отмените подписку в Google Play или Huawei AppGallery на вашем устройстве, а затем повторите попытку."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Хотите отменить текущую подписку и продолжить покупку?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Недоступно в текущем плане"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Активная подписка"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Изменить"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="Этот файл будет заменён новой версией."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Применить ко всем %@ дубликатам"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="Здесь только вы"; /* Meetings end call dialog description */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Заблокировано"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Неизвестное устройство"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Показать в облачном диске"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="Заканчивается"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3629,7 +3633,7 @@ /* Back button menu item title visible when navigating back to 1:1 chat . */ "chat.backButton.oneToOne.menu"="Чат с %@"; /* Schedule meeting waiting room setting title */ -"meetings.scheduleMeeting.waitingRoom"="Зал ожидания"; +"meetings.scheduleMeeting.waitingRoom"="Комната ожидания"; /* Schedule meeting waiting room setting description */ "meetings.scheduleMeeting.waitingRoom.description"="К встрече могут присоединиться только пользователи, допущенные организатором."; /* Schedule meeting waiting room warning banner title */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Войти"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Подождите, пока организатор впустит вас"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Подождите, пока организатор впустит вас"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Подождите, пока организатор начнёт встречу"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="Организатор не пустил вас"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="Понятно"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ ждёт подключения к звонку"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Отказать"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Пустить"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="Посмотреть комнату ожидания"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Пустить всех"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Не пускать %@?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Не пускать"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Отмена"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ ждёт подключения к звонку «%@»"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Открыть в MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/ru.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ru.lproj/Localizable.stringsdict similarity index 99% rename from iMEGA/Languages/ru.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ru.lproj/Localizable.stringsdict index 55c25e200c..d7c9fe2726 100644 --- a/iMEGA/Languages/ru.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/ru.lproj/Localizable.stringsdict @@ -3382,5 +3382,41 @@ %d файла сохранено в Облачном диске + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Скачивание запущено + few + Запущено %d скачивания + many + Запущено %d скачиваний + other + Запущено %d скачивания + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/iMEGA/Languages/th.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/th.lproj/Localizable.strings similarity index 95% rename from iMEGA/Languages/th.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/th.lproj/Localizable.strings index cfdf2a3324..6889772554 100644 --- a/iMEGA/Languages/th.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/th.lproj/Localizable.strings @@ -1207,7 +1207,7 @@ /* Title of a warning recommending upgrade to Pro */ "Upgrade to Pro"="อัปเกรดเป็นบัญชี Pro"; /* A description of MEGA features available only with a Pro plan. */ -"Access Pro only features like setting password protection and expiry dates for public files."="Access Pro only features like setting password protection and expiry dates for public links."; +"Access Pro only features like setting password protection and expiry dates for public files."="การเข้าถึงฟีเจอร์เฉพาะบัญชี Pro เช่น การตั้งค่าการป้องกันด้วยรหัสผ่านและวันหมดอายุสำหรับลิงก์สาธารณะ"; /* Alert title shown when the DEBUG mode is enabled */ "enableDebugMode_title"="เปิดใช้งานโหมดดีบัก"; /* Alert message shown when the DEBUG mode is enabled */ @@ -1295,7 +1295,7 @@ /* Label for any ‘Verify’ button, link, text, title, etc. - (String as short as possible). */ "verify"="อนุมัติ"; /* Button title */ -"verified"="Approved"; +"verified"="อนุมัติแล้ว"; /* A button title to delete the history of a chat. */ "clearChatHistory"="ล้างประวัติการแชท"; /* A confirmation message for a user to confirm that they want to clear the history of a chat. */ @@ -1849,7 +1849,7 @@ /* Message show when a user cannot activate the video in a group call because the max number of videos has been reached */ "Error. No more video are allowed in this group call."="คุณไม่ได้รับอนุญาตให้เปิดใช้งานวิดีโอเนื่องจากการโทรนี้มีผู้เข้าร่วมถึงจำนวนสูงสุดที่ใช้วิดีโอแล้ว"; /* Error shown when trying to start a call in a group with more peers than allowed */ -"Unable to start a call because the participants limit was exceeded."="Unable to start the call due to the participant limit having been exceeded."; +"Unable to start a call because the participants limit was exceeded."="ไม่สามารถเริ่มการโทรได้ เนื่องจากผู้เข้าร่วมเกินขีดจำกัดแล้ว"; /* Label shown while joining a public chat */ "Joining..."="กำลังเข้าร่วม…"; /* Label shown while leaving a public chat */ @@ -1937,7 +1937,7 @@ /* 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 cannot proceed because your Cloud storage 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."="อนุญาตให้ผู้ติดต่อของคุณเห็นว่า เป็นครั้งสุดท้ายที่คุณใช้งาน MEGA"; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ @@ -2047,7 +2047,7 @@ /* Text used as a section title or similar */ "Enter Email"="กรอกอีเมล"; /* Text to send as SMS message to user contacts inviting them to MEGA */ -"contact.invite.message"="You have a MEGA Chat request waiting. Register an account on MEGA and get 20 GB free lifetime storage."; +"contact.invite.message"="คุณมีคำขอ MEGA Chat รออยู่ ลงทะเบียนบัญชีที่ MEGA แล้วรับพื้นที่เก็บข้อมูลฟรี 20 GB ตลอดอายุการใช้งาน"; /* Destination folder name of chat files */ "My chat files"="ไฟล์แชทของฉัน"; /* Title of the section about the plan in the storage tab in My Account Section */ @@ -2585,11 +2585,11 @@ /* Over Disk Quota paywall not final detail message. */ "dialog.storage.paywall.notFinal.detail"="ขออภัย คุณไม่ได้ดำเนินการใด ๆ มา %@ วันแล้ว นับตั้งแต่ที่เราส่งอีเมลไปที่ %@ เมื่อ %@ คุณยังคงใช้พื้นที่เก็บข้อมูลขนาด %@ ซึ่งเกินขีดจำกัดพื้นที่ว่างของคุณ กรุณาดูอีเมลที่เราส่งข้อมูลถึงคุณเพิ่มเติมเกี่ยวกับสิ่งที่คุณต้องทำ"; /* Over Disk Quota paywall final detail message. */ -"dialog.storage.paywall.final.detail"="Your data will be deleted tomorrow if your account has not been upgraded."; +"dialog.storage.paywall.final.detail"="ถ้าบัญชีของคุณไม่ได้รับการอัปเกรด ข้อมูลของคุณจะถูกลบในวันพรุ่งนี้"; /* Over Disk Quota paywall, not final warning label. */ -"dialog.storage.paywall.notFinal.warning"="Upgrade within %@ to avoid your data getting deleted."; +"dialog.storage.paywall.notFinal.warning"="อัปเกรดภายใน %@ วันเพื่อหลีกเลี่ยงไม่ให้ข้อมูลของคุณถูกลบ"; /* Over Disk Quota paywall, final warning label. */ -"dialog.storage.paywall.final.warning"="Upgrade today if you wish to keep your data."; +"dialog.storage.paywall.final.warning"="อัปเกรดวันนี้หากคุณต้องการเก็บข้อมูลของคุณไว้"; /* Button title to see the available bonus */ "general.button.getBonus"="รับโบนัส"; /* Backup setup warning title */ @@ -2599,7 +2599,7 @@ /* Change the default backup location warning title. %@ is a folder */ "dialog.backup.folder.location.warning.title"="ย้าย “%@”"; /* Change the default backup location warning message */ -"dialog.root.backup.folder.location.warning.message"="You are changing a default backup folder location. This may affect your ability to find your backup folder. Please remember where it is located so that you can find it in the future."; +"dialog.root.backup.folder.location.warning.message"="คุณกำลังเปลี่ยนตำแหน่งโฟลเดอร์สำรองเริ่มต้น ซึ่งอาจส่งผลต่อความสามารถในการค้นหาโฟลเดอร์สำรองของคุณได้ โปรดอย่าลืมว่าตำแหน่งโฟลเดอร์อยู่ที่ใดเพื่อให้คุณสามารถค้นหาได้ในอนาคต"; /* Change the default backup location warning message */ "dialog.backup.folder.location.warning.message"="ในการย้ายโฟลเดอร์นี้จะเปลี่ยนปลายทางการสำรองข้อมูล การสำรองข้อมูลจะถูกปิดไว้เพื่อความปลอดภัย นี่คือสิ่งที่คุณต้องการที่จะดำเนินการต่อหรือไม่ หากต้องการสำรองข้อมูลอีกครั้ง สามารถเปิดใช้งานได้ที่แอป MEGA เดสก์ทอป"; /* Delete the default backup folder warning title. %@ is a folder */ @@ -2643,7 +2643,7 @@ /* Shown when an invalid/inexisting/not-available-anymore meeting link is opened. */ "meetings.alert.end"="สิ้นสุดการประชุม"; /* Meeting ended Alert button -- View Meeting Chat history. */ -"meetings.alert.meetingchat"="View chat history"; +"meetings.alert.meetingchat"="ดูประวัติการแชท"; /* Shown description when an invalid/inexisting/not-available-anymore meeting link is opened. */ "meetings.alert.end.description"="การประชุมที่คุณพยายามเข้าร่วมได้สิ้นสุดลงแล้ว คุณยังสามารถดูประวัติการแชทของการประชุมได้อยู่"; /* Contacts selection screen header: Invite contacts to meetings */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="คุณเคยสมัครแผน Pro ด้วย Google Play หรือ AppGallery กรุณายกเลิกการสมัครใช้งานด้วยตนเองได้ที่ Google Play หรือ Huawei AppGallery บนอุปกรณ์ของคุณก่อน แล้วลองอีกครั้ง"; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="คุณแน่ใจหรือไม่ว่าต้องการยกเลิกการสมัครใช้งานปัจจุบันและดำเนินการซื้อต่อ"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="ใช้ไม่ได้กับแผนปัจจุบันของคุณ"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="มีการสมัครใช้งานอยู่"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="อัปเดต"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="ไฟล์จะได้รับการอัปเดตเป็นเวอร์ชันใหม่"; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="นำไปใช้กับรายการที่ซ้ำกันทั้งหมด %@ รายการ"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="ตอนนี้มีแค่คุณที่อยู่ในสาย"; /* Meetings end call dialog description */ @@ -3153,9 +3153,9 @@ /* Start conversation: Schedule meeting context menu */ "meetings.startConversation.contextMenu.scheduleMeeting"="จัดกำหนดการประชุม"; /* Error message when trying to login and the account is blocked due to copyright violation */ -"account.suspension.message.copyright"="Your MEGA account has been suspended due to repeated allegations of copyright infringements. This means you cannot access your account or data within it.\n\nCheck your email for more information on how to file a counter-notice."; +"account.suspension.message.copyright"="บัญชี MEGA ของคุณถูกระงับเนื่องจากมีข้อกล่าวหาการละเมิดลิขสิทธิ์ซ้ำหลายครั้ง ซึ่งหมายความว่าคุณไม่สามารถเข้าถึงบัญชีหรือข้อมูลภายในได้ \n\nกรุณาตรวจสอบอีเมลของคุณเพื่อข้อมูลเพิ่มเติมเกี่ยวกับวิธีการยื่นข้อโต้แย้ง"; /* Error message when trying to login and the account is blocked due to any type of suspension, but copyright suspension */ -"account.suspension.message.nonCopyright"="Your account was terminated due to a breach of MEGA’s Terms of Service.\n\nYou will not be able to regain access to your stored data or be authorised to register a new MEGA account."; +"account.suspension.message.nonCopyright"="บัญชีของคุณถูกยุติการใช้งานเนื่องจากละเมิดเงื่อนไขการให้บริการของ MEGA\n\nคุณจะไม่สามารถเข้าถึงข้อมูลที่เก็บไว้หรือลงทะเบียนบัญชี MEGA ใหม่ได้อีกต่อไป"; /* Text shown in extensions to notify users that they have to open the application first */ "extensions.OpenApp.Message"="เปิดแอป MEGA และเข้าสู่ระบบเพื่อดำเนินการต่อ"; /* Account Storage title label of used storage size */ @@ -3175,21 +3175,21 @@ /* Chat Meetings tab - Section header title for the chat listing screen */ "chat.listing.sectionHeader.pastMeetings.title"="การประชุมที่ผ่านมา"; /* Context menu item. Allows the user to verify contacts of the shared folder. The %@ placeholder will be replaced with the contact's name */ -"general.menuAction.verifyContact.title"="Approve %@"; +"general.menuAction.verifyContact.title"="อนุมัติ %@"; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address that needs to be verified */ -"sharedItems.contactVerification.section.verifyContact.owner.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you share information with before they can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.owner.message"="เราปกป้องข้อมูลของคุณด้วยการเข้ารหัสแบบวิธีซีโร่-นอว์เลจ และเพื่อความปลอดภัยมากยิ่งขึ้น เราขอให้คุณอนุมัติผู้ติดต่อที่คุณแชร์ข้อมูลด้วยก่อนจึงจะสามารถเข้าถึงโฟลเดอร์ที่แชร์ได้"; /* On fingerprint verification screen for Shared Items. Header message located at the top of "My credentials" section */ -"sharedItems.contactVerification.section.myCredentials.message"="To approve your contact, ensure the credentials you see above match their account credentials. You can ask them to share their credentials with you."; +"sharedItems.contactVerification.section.myCredentials.message"="เพื่ออนุมัติผู้ติดต่อของคุณ กรุณาตรวจสอบว่าข้อมูลประจำตัวที่คุณเห็นด้านบนตรงกับข้อมูลประจำบัญชีของพวกเขา คุณสามารถขอให้พวกเขาแชร์ข้อมูลประจำบัญชีกับคุณได้"; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address if the owner of the received shared item is unverified */ -"sharedItems.contactVerification.section.verifyContact.receiver.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you receive information from before you can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.receiver.message"="เราปกป้องข้อมูลของคุณด้วยการเข้ารหัสแบบวิธีซีโร่-นอว์เลจ และเพื่อความปลอดภัยมากยิ่งขึ้น เราขอให้คุณอนุมัติผู้ติดต่อที่คุณแชร์ข้อมูลด้วยก่อนจึงจะสามารถเข้าถึงโฟลเดอร์ที่แชร์ได้"; /* On fingerprint verification screen for Incoming Shared Items. Yellow banner message that will be shown to the receiver if the owner of the folder haven't verified them yet. */ "sharedItems.contactVerification.section.verifyContact.bannerMessage"=""; /* On Cloud Drive screen for incoming shared folder, if the contact which shared the folder is not verified, this is the message that wwe display */ -"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@ is shared by a contact you haven’t approved. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@ ถูกแชร์โดยผู้ติดต่อที่คุณยังไม่ได้รับการอนุมัติ เพื่อความปลอดภัยมากยิ่งขึ้น เราขอแนะนำให้คุณอนุมัติข้อมูลประจำตัวของพวกเขาในรายชื่อติดต่อ โดยแตะที่ ⓘ ถัดจากรายชื่อติดต่อที่คุณต้องการอนุมัติ"; /* Title of the label in verification screen. It shows the credentials of the current user so it can be used to be verified by other contacts */ "verifyCredentials.yourCredentials.title"="ข้อมูลประจำตัวของคุณ"; /* Title of the header label in contact verification screen. */ -"verifyCredentials.headerMessage"="We protect your data with zero-knowledge encryption so all the information you store, share, and receive on MEGA is secure. To ensure extra security, we ask you to approve the contacts you share information with or receive data from."; +"verifyCredentials.headerMessage"="เราใช้การเข้ารหัสแบบวิธีซีโร่-นอว์เลจเพื่อรักษาความปลอดภัยข้อมูลทั้งหมดที่คุณจัดเก็บ แชร์ และรับบน MEGA และเพื่อเป็นการเพิ่มความปลอดภัยให้ดียิ่งขึ้น เราขอให้คุณอนุมัติผู้ติดต่อที่คุณแชร์ข้อมูลหรือรับข้อมูลด้วย"; /* On Incoming Shared Items Tab. Name of the folder if the the owner and the receiver has not yet verified each other. */ "sharedItems.tab.incoming.undecryptedFolderName"="[โฟลเดอร์ที่ไม่ได้ถอดรหัส]"; /* On Outgoing Shared Items Tab. Text that shows the receiver's name of the shared folder. %@ will be replaced with the receiver's name. */ @@ -3317,9 +3317,9 @@ /* Text description for a meeting showing that is a monthly recurring meeting */ "meetings.scheduled.recurring.monthly"="ทุกเดือน"; /* On Outgoing Shared Items Tab. Title of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ -"sharedItems.tab.outgoing.modal.cannotVerifyContact.title"="Cannot approve contact"; +"sharedItems.tab.outgoing.modal.cannotVerifyContact.title"="อนุมัติผู้ติดต่อไม่ได้"; /* On Outgoing Shared Items Tab. Message of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ -"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="You can’t approve %@ as they’re not in your contact list. Wait for them to accept your invitation first."; +"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="คุณไม่สามารถอนุมัติ %@ ได้เนื่องจากไม่ได้อยู่ในรายชื่อผู้ติดต่อของคุณ รอให้พวกเขาตอบรับคำเชิญของคุณก่อน"; /* Title shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled */ "picker.disable.passcode.title"="ปิดใช้งานรหัส"; /* Description shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled, it shows the users how to disable the passcode */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="บล็อกแล้ว"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="อุปกรณ์ที่ไม่รู้จัก"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="แสดงในคลาวด์ไดร์ฟ"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="แสดงในสำรองข้อมูล"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="สิ้นสุดการทำประจำ"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3629,23 +3633,23 @@ /* Back button menu item title visible when navigating back to 1:1 chat . */ "chat.backButton.oneToOne.menu"="แชทอยู่กับ %@"; /* Schedule meeting waiting room setting title */ -"meetings.scheduleMeeting.waitingRoom"="Waiting room"; +"meetings.scheduleMeeting.waitingRoom"="ห้องนั่งรอ"; /* Schedule meeting waiting room setting description */ -"meetings.scheduleMeeting.waitingRoom.description"="Only users admitted by the host can join the meeting."; +"meetings.scheduleMeeting.waitingRoom.description"="เฉพาะผู้ใช้ที่ได้รับอนุญาตจากผู้จัดการประชุมเท่านั้นที่สามารถเข้าร่วมการประชุมได้"; /* Schedule meeting waiting room warning banner title */ -"meetings.scheduleMeeting.waitingRoomWarningBanner.title"="Participants added by non-hosts during calls won’t be sent to the waiting room."; +"meetings.scheduleMeeting.waitingRoomWarningBanner.title"="ในระหว่างการโทร ผู้เข้าร่วมที่เพิ่มจากผู้ที่ไม่ใช่ผู้จัดการประชุมจะไม่ถูกจัดให้อยู่ในห้องนั่งรอ "; /* Schedule meeting waiting room warning banner learn more */ "meetings.scheduleMeeting.waitingRoomWarningBanner.learnMore"="เรียนรู้เพิ่มเติม"; /* Album saved to cloud drive alert message */ -"albumLink.alert.message.albumSavedToCloudDrive"="“%@” saved to Cloud drive"; +"albumLink.alert.message.albumSavedToCloudDrive"="บันทึก “%@” ไปไว้ที่คลาวด์ไดรฟ์แล้ว"; /* Album failed to save to cloud drive alert message */ -"albumLink.alert.message.albumFailedToSaveToCloudDrive"="“%@” can’t be saved to Cloud drive. Try again later and if the problem continues, contact the person who shared the link with you."; +"albumLink.alert.message.albumFailedToSaveToCloudDrive"="ไม่สามารถบันทึก “%@” ลงในคลาวด์ไดร์ฟได้ กรุณาลองอีกครั้งในภายหลัง แต่หากยังประสบปัญหาอยู่ ให้ติดต่อบุคคลที่แชร์ลิงก์กับคุณ"; /* Text displayed on banner we show when sharing folder with unverified contacts */ -"shareFolder.contactsNotVerified"="Some of the contacts you’re sharing information with haven’t been approved by you. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"shareFolder.contactsNotVerified"="ผู้ติดต่อบางรายที่คุณแชร์ข้อมูลด้วยยังไม่ได้รับการอนุมัติจากคุณ เพื่อความปลอดภัยมากยิ่งขึ้น เราขอแนะนำให้คุณอนุมัติข้อมูลประจำตัวของพวกเขาในรายชื่อติดต่อ โดยแตะที่ ⓘ ถัดจากรายชื่อติดต่อที่คุณต้องการอนุมัติ"; /* Rename public album alert title */ "albumLink.alert.renameAlbum.title"="เปลี่ยนชื่ออัลบั้ม"; /* Rename public album alert message */ -"albumLink.alert.renameAlbum.message"="An album named “%@” already exists in your Albums. Enter a new name for the album you’re saving."; +"albumLink.alert.renameAlbum.message"="มีอัลบั้มที่ชื่อ “%@” อยู่แล้วในอัลบั้มของคุณ กรุณากรอกชื่ออัลบั้มที่คุณกำลังจะบันทึกใหม่"; /* Button title of the dialog for transfer quota error that will direct to upgrade plan */ "transferQuotaError.button.upgrade"="อัปเกรด"; /* Button title of the dialog for transfer quota error that will dismiss dialog */ @@ -3653,29 +3657,29 @@ /* Button title of the dialog for transfer quota error to buy new plan */ "transferQuotaError.button.buyNewPlan"="ซื้อแผนใหม่"; /* Footer message of the dialog for transfer quota error. [A] will be replaced by the usage quota percent. [B] will be replaced by the max transfer quota in TB. For example: 100% of 16 TB used */ -"transferQuotaError.footerMessage.quotaUsage"="[A]%% of [B] TB used"; +"transferQuotaError.footerMessage.quotaUsage"="ใช้ไปแล้ว [A]%% จาก [B] TB "; /* Title of the dialog for transfer quota error with limited available trasfer quota */ "transferQuotaError.downloadLimitedQuota.title"="จำกัดโควต้าการถ่ายโอน"; /* Message text of the dialog for transfer quota error with limited available trasfer quota for free accounts */ -"transferQuotaError.downloadLimitedQuota.freeAccount.message"="Downloading may be interrupted as you’ve used most of your transfer quota for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota."; +"transferQuotaError.downloadLimitedQuota.freeAccount.message"="การดาวน์โหลดอาจถูกหยุดชั่วคราว เนื่องจากที่อยู่ IP นี้ไม่มีโควต้าการถ่ายโอนที่เพียงพอ หากคุณต้องการโควต้าเพิ่มเติม คุณสามารถอัปเกรดเป็นแผน Pro หรือรออีก %@ เพื่อให้ที่อยู่ IP ของคุณสามารถจัดสรรโควต้าฟรีใหม่ได้ [A]เรียนรู้เพิ่มเติม[/A]เกี่ยวกับโควต้าการถ่ายโอน"; /* Message text of the dialog for transfer quota error with limited available trasfer quota for pro accounts */ -"transferQuotaError.downloadLimitedQuota.proAccount.message"="Downloading may be interrupted as you’ve used most of your transfer quota on this account. To get more quota, purchase a new plan, or if you have a recurring subscription with MEGA, you can wait for your plan to renew."; +"transferQuotaError.downloadLimitedQuota.proAccount.message"="การดาวน์โหลดอาจถูกหยุดชั่วคราว เนื่องจากคุณได้ใช้โควต้าการถ่ายโอนส่วนใหญ่ในบัญชีนี้แล้ว หากต้องการรับโควต้าเพิ่ม กรุณาซื้อแผนใหม่ หรือหากคุณมีการสมัครใช้งานกับ MEGA อยู่แล้ว กรุณารอให้แผนของคุณมีการต่ออายุใหม่ก่อน"; /* Title of the dialog for transfer quota error with exceeded quota */ "transferQuotaError.downloadExceededQuota.title"="เกินโควต้าการถ่ายโอนแล้ว"; /* Message text of the dialog for transfer quota error with exceeded download trasfer quota for free accounts */ -"transferQuotaError.downloadExceededQuota.freeAccount.message"="You can’t continue downloading as you don’t have any transfer quota left for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota."; +"transferQuotaError.downloadExceededQuota.freeAccount.message"="คุณไม่สามารถดาวน์โหลดต่อได้ เนื่องจากที่อยู่ IP นี้ไม่มีโควต้าการถ่ายโอนที่เพียงพอ หากต้องการขอโควต้าเพิ่มเติม คุณสามารถอัปเกรดเป็นแผน Pro หรือรออีก %@ เพื่อให้ที่อยู่ IP ของคุณสามารถจัดสรรโควต้าฟรีใหม่ได้ [A]เรียนรู้เพิ่มเติม[/A]เกี่ยวกับโควต้าการถ่ายโอน"; /* Message text of the dialog for transfer quota error with exceeded download trasfer quota for pro accounts */ -"transferQuotaError.downloadExceededQuota.proAccount.message"="You can’t continue downloading as you don’t have any transfer quota left on this account. To get more quota, purchase a new plan, or if you have a recurring subscription with MEGA, you can wait for your plan to renew."; +"transferQuotaError.downloadExceededQuota.proAccount.message"="คุณไม่สามารถดาวน์โหลดต่อได้ เนื่องจากบัญชีนี้ไม่มีโควต้าการถ่ายโอนที่เพียงพอ หากต้องการรับโควต้าเพิ่ม กรุณาซื้อแผนใหม่ หรือหากคุณมีการสมัครใช้งานกับ MEGA อยู่แล้ว กรุณารอให้แผนของคุณมีการต่ออายุใหม่ก่อน"; /* Message text of the dialog for transfer quota error with exceeded streaming trasfer quota for free accounts */ -"transferQuotaError.streamingExceededQuota.freeAccount.message"="Your media stopped playing as you don’t have any transfer quota left for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota."; +"transferQuotaError.streamingExceededQuota.freeAccount.message"="สื่อของคุณหยุดเล่นชั่วคราว เนื่องจากที่อยู่ IP นี้ไม่มีโควต้าการถ่ายโอนที่เพียงพอ หากต้องการขอโควต้าเพิ่มเติม คุณสามารถอัปเกรดเป็นแผน Pro หรือรออีก %@ เพื่อให้ที่อยู่ IP ของคุณสามารถจัดสรรโควต้าฟรีใหม่ได้ [A]เรียนรู้เพิ่มเติม[/A]เกี่ยวกับโควต้าการถ่ายโอน"; /* Message text of the dialog for transfer quota error with exceeded download trasfer quota for pro accounts */ -"transferQuotaError.streamingExceededQuota.proAccount.message"="Your media stopped playing as you don’t have any transfer quota left on this account. To get more quota, purchase a new plan, or if you have a recurring subscription with MEGA, you can wait for your plan to renew."; +"transferQuotaError.streamingExceededQuota.proAccount.message"="สื่อของคุณหยุดเล่นชั่วคราว เนื่องจากบัญชีนี้ไม่มีโควต้าการถ่ายโอนที่เพียงพอ หากต้องการรับโควต้าเพิ่ม กรุณาซื้อแผนใหม่ หรือหากคุณมีการสมัครใช้งานกับ MEGA อยู่แล้ว กรุณารอให้แผนของคุณมีการต่ออายุใหม่ก่อน"; /* Meeting waiting room leave button */ "meetings.waitingRoom.leave"="ออก"; /* Meeting waiting room leave alert message */ "meetings.waitingRoom.alert.leaveMeeting"="ออกจากการประชุมหรือไม่"; /* Meeting waiting room don't leave button */ -"meetings.waitingRoom.alert.dontLeave"="Don’t leave"; +"meetings.waitingRoom.alert.dontLeave"="ไม่ต้องออก"; /* Meeting waiting room guest join first name textfield */ "meetings.waitingRoom.guest.firstName"="ชื่อ"; /* Meeting waiting room guest join last name textfield */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="เข้าร่วม"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wait for host to let you in"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="รอให้ผู้จัดการประชุมอนุญาตคุณเข้าร่วมก่อน"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="รอให้ผู้จัดเริ่มการประชุม"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="ผู้จัดการประชุมไม่อนุญาตให้คุณเข้า"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="โอเค เข้าใจแล้ว"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ กำลังรอเข้าร่วมสาย"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="ปฏิเสธ"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="ยอมรับ"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="ดูห้องนั่งรอ"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="ยอมรับทั้งหมด"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="ปฏิเสธการเข้าร่วมสายของ %@ หรือไม่"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="ปฏิเสธเข้าร่วมสาย"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="ยกเลิก"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ กำลังรอเข้าร่วมสาย “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="เปิดใน MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/th.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/th.lproj/Localizable.stringsdict similarity index 97% rename from iMEGA/Languages/th.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/th.lproj/Localizable.stringsdict index b233b53907..7e44f08e8f 100644 --- a/iMEGA/Languages/th.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/th.lproj/Localizable.stringsdict @@ -26,10 +26,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - %d item removed other - %d items removed + ลบ %d รายการออกแล้ว sharedItems.rubbish.confirmation.fileCount @@ -938,10 +936,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Every day effective [B][StartDate] until [UntilDate][/B] from [B][StartTime] to [EndTime][/B] other - Every %d days effective [B][StartDate] until [UntilDate][/B] from [B][StartTime] to [EndTime][/B] + ทุก %d วัน มีผลตั้งแต่ [B][StartDate] จนถึง [UntilDate][/B] ตั้งแต่ [B][StartTime] จนถึง [EndTime][/B] meetings.scheduled.recurring.daily.forever @@ -954,10 +950,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Every day effective [B][StartDate][/B] from [B][StartTime] to [EndTime][/B] other - Every %d days effective [B][StartDate][/B] from [B][StartTime] to [EndTime][/B] + ทุก %d วัน มีผล [B][StartDate][/B] ตั้งแต่ [B][StartTime] จนถึง [EndTime][/B] meetings.scheduled.recurring.weekly.oneDay.until @@ -2258,10 +2252,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Meeting will occur every month on day [cardinalDay]. other - Meeting will occur every %d months on day [cardinalDay]. + การประชุมจะมีขึ้นในวันที่ [cardinalDay] ของทุก %d เดือน meetings.scheduled.create.monthly.multipleDaysCardinal.footerNote @@ -2274,10 +2266,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Meeting will occur every month on day [cardinalDays] and day [cardinalLastDay]. other - Meeting will occur every %d months on day [cardinalDays] and day [cardinalLastDay]. + การประชุมจะมีขึ้นในวันที่ [cardinalDays] และที่ [cardinalLastDay] ของทุก %d เดือน meetings.scheduled.create.monthly.weekNumberAndWeekDay.footerNote @@ -2332,10 +2322,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Every month on day [cardinalNumber] other - Every %d months on day [cardinalNumber] + ทุกวันที่ [cardinalNumber] ของทุก %d เดือน meetings.scheduled.create.monthly.weekNumberAndWeekDay.selectedFrequency @@ -2376,10 +2364,36 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - %d file saved to Cloud drive other - %d files saved to Cloud drive + ไฟล์ %d รายการ ถูกบันทึกไว้ที่คลาวด์ไดรฟ์แล้ว + + + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + เริ่มต้นดาวน์โหลด %d รายการแล้ว + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + เลือกอยู่ %d รายการ diff --git a/iMEGA/Languages/vi.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/vi.lproj/Localizable.strings similarity index 97% rename from iMEGA/Languages/vi.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/vi.lproj/Localizable.strings index 54613fb917..b6b3c11a7a 100644 --- a/iMEGA/Languages/vi.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/vi.lproj/Localizable.strings @@ -1207,7 +1207,7 @@ /* Title of a warning recommending upgrade to Pro */ "Upgrade to Pro"="Nâng cấp lên hạng Pro"; /* A description of MEGA features available only with a Pro plan. */ -"Access Pro only features like setting password protection and expiry dates for public files."="Access Pro only features like setting password protection and expiry dates for public links."; +"Access Pro only features like setting password protection and expiry dates for public files."="Sử dụng các tính mà chỉ có hạng Pro mới có, như đặt mật khẩu và đặt thời gian hết hạn truy cập cho từng đường liên kết chia sẻ công khai."; /* Alert title shown when the DEBUG mode is enabled */ "enableDebugMode_title"="Bật chế độ debug"; /* Alert message shown when the DEBUG mode is enabled */ @@ -1517,7 +1517,7 @@ /* SDK error returned upon a HTTP error */ "HTTP error"="Lỗi HTTP"; /* Label to show that an error related with an unknown error occurs during a SDK operation. */ -"Unknown error"="Lỗi không xác định được"; +"Unknown error"="Lỗi không xác định được"; /* Label shown when you call someone (outgoing call), before the call starts. */ "calling..."="Đang gọi…"; /* notification subtitle of incoming calls */ @@ -1849,7 +1849,7 @@ /* Message show when a user cannot activate the video in a group call because the max number of videos has been reached */ "Error. No more video are allowed in this group call."="Không thể bật video cho cuộc gọi này được vì quá mức số lượng thành viên tham gia cuộc gọi đang sử dụng video."; /* Error shown when trying to start a call in a group with more peers than allowed */ -"Unable to start a call because the participants limit was exceeded."="Unable to start the call due to the participant limit having been exceeded."; +"Unable to start a call because the participants limit was exceeded."="Không thể bắt đầu cuộc gọi do vượt quá số lượng thành viên."; /* Label shown while joining a public chat */ "Joining..."="Đang tham gia…"; /* Label shown while leaving a public chat */ @@ -1937,7 +1937,7 @@ /* 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"="Your upload cannot proceed because your Cloud storage is full"; +"Your upload(s) cannot proceed because your account is full"="Phiên tải lên không thể tiến hành vì không gian Ổ Mây của bạ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."="Cho phép các tên trong liên lạc biết được lần thấy cuối bạn ở MEGA."; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ @@ -2047,7 +2047,7 @@ /* Text used as a section title or similar */ "Enter Email"="Nhập Email"; /* Text to send as SMS message to user contacts inviting them to MEGA */ -"contact.invite.message"="You have a MEGA Chat request waiting. Register an account on MEGA and get 20 GB free lifetime storage."; +"contact.invite.message"="Bạn có một lời đang chờ bạn hồi âm trên MEGA Chat. Đăng ký một tài khoản trên MEGA và nhận 20 GB không gian lưu trữ miễn phí vô thời hạn."; /* Destination folder name of chat files */ "My chat files"="Tệp đã gửi trong chát"; /* Title of the section about the plan in the storage tab in My Account Section */ @@ -2585,11 +2585,11 @@ /* Over Disk Quota paywall not final detail message. */ "dialog.storage.paywall.notFinal.detail"="Quý khách đã không giải quyết tình trạng tài khoản của mình trong %@ từ khi chúng tôi gửi email thông báo tới địa chỉ %@ của quý khách, vào ngày %@. Quý khách hiện vẫn đang dùng %@ trong không gian lưu trữ, vượt quá mức hạn của tài khoản miễn phí. Xin kiểm tra hộp thư email để xem thư chúng tôi đã gửi để biết thêm chi tiết và các cách xử lý."; /* Over Disk Quota paywall final detail message. */ -"dialog.storage.paywall.final.detail"="Your data will be deleted tomorrow if your account has not been upgraded."; +"dialog.storage.paywall.final.detail"="Dữ liệu của bạn sẽ bị xóa vào ngày mai nếu tài khoản của bạn không có được nâng cấp."; /* Over Disk Quota paywall, not final warning label. */ -"dialog.storage.paywall.notFinal.warning"="Upgrade within %@ to avoid your data getting deleted."; +"dialog.storage.paywall.notFinal.warning"="Nâng cấp trong vòng %@ để tránh tình trạng dữ liệu bị xóa bỏ."; /* Over Disk Quota paywall, final warning label. */ -"dialog.storage.paywall.final.warning"="Upgrade today if you wish to keep your data."; +"dialog.storage.paywall.final.warning"="Nâng cấp tài khoản ngay hôm nay nếu muốn lưu giữ dữ liệu của mình."; /* Button title to see the available bonus */ "general.button.getBonus"="Nhận thêm Thưởng"; /* Backup setup warning title */ @@ -2599,7 +2599,7 @@ /* Change the default backup location warning title. %@ is a folder */ "dialog.backup.folder.location.warning.title"="Di chuyển “%@”"; /* Change the default backup location warning message */ -"dialog.root.backup.folder.location.warning.message"="You are changing a default backup folder location. This may affect your ability to find your backup folder. Please remember where it is located so that you can find it in the future."; +"dialog.root.backup.folder.location.warning.message"="Bạn đang tiến hành thay đổi vị trí mặc định của thư mục lưu trữ sao lưu. Việc này có thể ảnh hưởng đến khả năng truy tìm thư mục lưu trữ sao lưu. Xin ghi nhớ vị trí mà bạn dời thư mục đi để không bị lạc mất dữ liệu trong tương lai."; /* Change the default backup location warning message */ "dialog.backup.folder.location.warning.message"="Di chuyển thư mục này sẽ thay đổi vị trí của việc lưu dự phòng, và sẽ bị tắt đi để đảm bảo an toàn. Có chắc chắn đây là điều bạn muốn thực hiện hay không? Việc lưu dự phòng có thể được bật lại trong App MEGA cho Máy Tính."; /* Delete the default backup folder warning title. %@ is a folder */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="Quý khách đã mua hạng Pro thông qua CH Google Play hoặc AppGallery từ trước. Xin vui lòng hủy thủ công dịch vụ với bên Google Play hoặc Huawei AppGallery trên thiết bị của mình rồi quay lại đây để tiếp tục việc mua."; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="Quý khách có muốn hủy gói đăng ký dịch vụ hiện tại để mua gói mới không?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="Không có sẵn cho gói hiện tại của bạn"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="Gói Đăng Ký Dịch Vụ Còn Hoạt Động"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="Cập nhật"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="Tệp tin sẽ được cập nhật với phiên bản mới."; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="Áp dụng cho tất cả %@ mục trùng lặp"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="Bạn là người duy nhất ở đây"; /* Meetings end call dialog description */ @@ -3153,9 +3153,9 @@ /* Start conversation: Schedule meeting context menu */ "meetings.startConversation.contextMenu.scheduleMeeting"="Đặt hẹn cuộc họp"; /* Error message when trying to login and the account is blocked due to copyright violation */ -"account.suspension.message.copyright"="Your MEGA account has been suspended due to repeated allegations of copyright infringements. This means you cannot access your account or data within it.\n\nCheck your email for more information on how to file a counter-notice."; +"account.suspension.message.copyright"="Tài khoản MEGA của quý vị đã bị đình chỉ do đã nhiều lần bị cáo buộc vi phạm bản quyền. Điều này có nghĩa là quý vị không được phép truy cập tài khoản của mình hoặc bất cứ dữ liệu bên trong.\n\nKiểm tra email của quý vị để biết thêm thông tin và về cách gửi đơn phản đối."; /* Error message when trying to login and the account is blocked due to any type of suspension, but copyright suspension */ -"account.suspension.message.nonCopyright"="Your account was terminated due to a breach of MEGA’s Terms of Service.\n\nYou will not be able to regain access to your stored data or be authorised to register a new MEGA account."; +"account.suspension.message.nonCopyright"="Tài khoản của quý vị đã bị chấm dứt do vi phạm Điều Khoản Dịch Vụ của MEGA.\n\nQuý vị sẽ không thể lấy lại quyền truy cập vào dữ liệu đã được lưu trữ của mình hoặc được phép đăng ký tài khoản MEGA mới."; /* Text shown in extensions to notify users that they have to open the application first */ "extensions.OpenApp.Message"="Mở app MEGA và đăng nhập để tiếp tục."; /* Account Storage title label of used storage size */ @@ -3175,17 +3175,17 @@ /* Chat Meetings tab - Section header title for the chat listing screen */ "chat.listing.sectionHeader.pastMeetings.title"="Các cuộc họp trước đây"; /* Context menu item. Allows the user to verify contacts of the shared folder. The %@ placeholder will be replaced with the contact's name */ -"general.menuAction.verifyContact.title"="Approve %@"; +"general.menuAction.verifyContact.title"="Chấp nhận %@"; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address that needs to be verified */ -"sharedItems.contactVerification.section.verifyContact.owner.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you share information with before they can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.owner.message"="Chúng tôi bảo vệ dữ liệu của bạn bằng thuật toán mã hóa vô kiến thức. Để đảm bảo an toàn hơn, chúng tôi yêu cầu bạn chấp nhận các tên liên lạc mà bạn chia sẻ thông tin trước khi họ được cấp quyền truy cập vào các thư mục chia sẻ."; /* On fingerprint verification screen for Shared Items. Header message located at the top of "My credentials" section */ "sharedItems.contactVerification.section.myCredentials.message"="Để chấp nhận tên liên lạc, đảm bảo rằng chứng chỉ bạn thấy dưới đây trùng khớp với chứng chỉ tài khoản của họ. Bạn có thể hỏi người đó chia sẻ chứng chỉ cho bạn."; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address if the owner of the received shared item is unverified */ -"sharedItems.contactVerification.section.verifyContact.receiver.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you receive information from before you can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.receiver.message"="Chúng tôi bảo vệ dữ liệu của bạn bằng thuật toán mã hóa vô kiến thức. Để đảm bảo an toàn hơn, chúng tôi yêu cầu bạn chấp nhận các tên liên lạc mà bạn có nhận thông tin trước khi bạn truy cập vào các thư mục chia sẻ."; /* On fingerprint verification screen for Incoming Shared Items. Yellow banner message that will be shown to the receiver if the owner of the folder haven't verified them yet. */ "sharedItems.contactVerification.section.verifyContact.bannerMessage"=""; /* On Cloud Drive screen for incoming shared folder, if the contact which shared the folder is not verified, this is the message that wwe display */ -"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@ is shared by a contact you haven’t approved. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@ là mục được chia sẻ từ một tên liên lạc chưa được bạn chấp nhận. Để đảm bảo an toàn hơn, chúng tôi khuyên bạn nên chấp nhận chứng chỉ đăng nhập của họ trong Sổ Liên Lạc bằng cách nhấn vào ⓘ bên cạnh tên liên lạc bạn muốn chấp nhận."; /* Title of the label in verification screen. It shows the credentials of the current user so it can be used to be verified by other contacts */ "verifyCredentials.yourCredentials.title"="Chứng thực của bạn"; /* Title of the header label in contact verification screen. */ @@ -3319,7 +3319,7 @@ /* On Outgoing Shared Items Tab. Title of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ "sharedItems.tab.outgoing.modal.cannotVerifyContact.title"="Không thể chấp nhận tên liên lạc"; /* On Outgoing Shared Items Tab. Message of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ -"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="You can’t approve %@ as they’re not in your contact list. Wait for them to accept your invitation first."; +"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="Bạn không thể chấp nhận %@ vì họ không có trong sổ liên lạc của bạn. Hãy chờ đến khi họ chấp nhận lời mời của bạn trước."; /* Title shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled */ "picker.disable.passcode.title"="Tắt mật mã MEGA"; /* Description shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled, it shows the users how to disable the passcode */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="Bị chặn"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="Không rõ thiết bị"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="Hiện vị trí trong Ổ Mây"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="Ngừng tái diễn"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3641,7 +3645,7 @@ /* Album failed to save to cloud drive alert message */ "albumLink.alert.message.albumFailedToSaveToCloudDrive"="Không thể lưu “%@” vào Ổ Mây. Hãy thử lại sau và nếu sự cố vẫn tiếp diễn, hãy liên hệ với người đã chia sẻ đường liên kết cho bạn."; /* Text displayed on banner we show when sharing folder with unverified contacts */ -"shareFolder.contactsNotVerified"="Some of the contacts you’re sharing information with haven’t been approved by you. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"shareFolder.contactsNotVerified"="Một số tên liên lạc mà bạn đang chia sẻ thông tin chưa được bạn đã chấp nhận. Để đảm bảo an toàn hơn, chúng tôi khuyên bạn nên chấp nhận chứng chỉ đăng nhập của họ trong Sổ Liên Lạc bằng cách vào dấu ⓘ bên cạnh tên liên lạc bạn muốn chấp nhận."; /* Rename public album alert title */ "albumLink.alert.renameAlbum.title"="Đổi tên album"; /* Rename public album alert message */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="Tham gia"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Đợi cho người chủ trì cho bạn vào"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="Đợi cho người chủ trì cho bạn vào"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="Đợi cho người chủ trì bắt đầu cuộc họp"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="Người chủ trì đã không cho bạn vào"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="OK, hiểu rồi"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ đang chờ để tham gia cuộc gọi"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="Từ chối"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="Chấp nhận"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="Xem phòng chờ"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="Chấp nhận tất cả"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Từ chối %@ tham gia?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="Từ chối tham gia"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="Hủy"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ đang chờ để tham gia “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Mở trong MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/vi.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/vi.lproj/Localizable.stringsdict similarity index 98% rename from iMEGA/Languages/vi.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/vi.lproj/Localizable.stringsdict index 4d672ad2b7..ac06cea4fa 100644 --- a/iMEGA/Languages/vi.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/vi.lproj/Localizable.stringsdict @@ -2368,5 +2368,35 @@ %d tệp tin đã được lưu vào Ổ Mây + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + %d phiên tải xuống đã được bắt đầu + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/iMEGA/Languages/zh-Hans.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hans.lproj/Localizable.strings similarity index 97% rename from iMEGA/Languages/zh-Hans.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hans.lproj/Localizable.strings index 3736b2bf4e..e8668d6690 100644 --- a/iMEGA/Languages/zh-Hans.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hans.lproj/Localizable.strings @@ -1207,7 +1207,7 @@ /* Title of a warning recommending upgrade to Pro */ "Upgrade to Pro"="升级到Pro会员"; /* A description of MEGA features available only with a Pro plan. */ -"Access Pro only features like setting password protection and expiry dates for public files."="Access Pro only features like setting password protection and expiry dates for public links."; +"Access Pro only features like setting password protection and expiry dates for public files."="享受Pro会员专属功能,例如为公共链接设置密码保护与链接到期日。"; /* Alert title shown when the DEBUG mode is enabled */ "enableDebugMode_title"="开启调试模式"; /* Alert message shown when the DEBUG mode is enabled */ @@ -1849,7 +1849,7 @@ /* Message show when a user cannot activate the video in a group call because the max number of videos has been reached */ "Error. No more video are allowed in this group call."="由于通话中使用视频的参与人数已达上限,因此您无法启用视频。"; /* Error shown when trying to start a call in a group with more peers than allowed */ -"Unable to start a call because the participants limit was exceeded."="Unable to start the call due to the participant limit having been exceeded."; +"Unable to start a call because the participants limit was exceeded."="由于超出参与人数限制,无法发起通话。"; /* Label shown while joining a public chat */ "Joining..."="正在加入…"; /* Label shown while leaving a public chat */ @@ -1937,7 +1937,7 @@ /* 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 cannot proceed because your Cloud storage 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."="允许您的联系人查看您在MEGA的上次登录时间。"; /* Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' */ @@ -2047,7 +2047,7 @@ /* Text used as a section title or similar */ "Enter Email"="输入电子邮件地址"; /* Text to send as SMS message to user contacts inviting them to MEGA */ -"contact.invite.message"="You have a MEGA Chat request waiting. Register an account on MEGA and get 20 GB free lifetime storage."; +"contact.invite.message"="您有一条MEGA chat消息等待查收。注册MEGA帐户即可获得20 GB永久免费存储空间。"; /* Destination folder name of chat files */ "My chat files"="我的聊天文件"; /* Title of the section about the plan in the storage tab in My Account Section */ @@ -2585,11 +2585,11 @@ /* Over Disk Quota paywall not final detail message. */ "dialog.storage.paywall.notFinal.detail"="自从我们在%@向您的电子邮件地址%@发送邮件后,您似乎并没有在%@内采取任何行动。目前,您仍然使用%@存储空间,这超出了免费存储空间上限。请查看我们发送给您的电子邮件以了解更多信息。"; /* Over Disk Quota paywall final detail message. */ -"dialog.storage.paywall.final.detail"="Your data will be deleted tomorrow if your account has not been upgraded."; +"dialog.storage.paywall.final.detail"="如果您的帐户尚未升级,您的数据将在明天被删除。"; /* Over Disk Quota paywall, not final warning label. */ -"dialog.storage.paywall.notFinal.warning"="Upgrade within %@ to avoid your data getting deleted."; +"dialog.storage.paywall.notFinal.warning"="在%@内升级您的帐户以避免您的数据被删除。"; /* Over Disk Quota paywall, final warning label. */ -"dialog.storage.paywall.final.warning"="Upgrade today if you wish to keep your data."; +"dialog.storage.paywall.final.warning"="如果您希望保留您的数据,请立即升级。"; /* Button title to see the available bonus */ "general.button.getBonus"="获得奖励"; /* Backup setup warning title */ @@ -2599,7 +2599,7 @@ /* Change the default backup location warning title. %@ is a folder */ "dialog.backup.folder.location.warning.title"="移动“%@”"; /* Change the default backup location warning message */ -"dialog.root.backup.folder.location.warning.message"="You are changing a default backup folder location. This may affect your ability to find your backup folder. Please remember where it is located so that you can find it in the future."; +"dialog.root.backup.folder.location.warning.message"="您正在更改默认备份文件夹位置。这可能会影响您查找备份文件夹。请牢记此位置以便您将来可以找到它。"; /* Change the default backup location warning message */ "dialog.backup.folder.location.warning.message"="移动此文件夹将更改备份位置。出于安全考虑,备份将被停止。您确定要继续吗?使用MEGA桌面应用程序可重新开启备份。"; /* Delete the default backup folder warning title. %@ is a folder */ @@ -2643,7 +2643,7 @@ /* Shown when an invalid/inexisting/not-available-anymore meeting link is opened. */ "meetings.alert.end"="会议已结束"; /* Meeting ended Alert button -- View Meeting Chat history. */ -"meetings.alert.meetingchat"="View chat history"; +"meetings.alert.meetingchat"="查看聊天记录"; /* Shown description when an invalid/inexisting/not-available-anymore meeting link is opened. */ "meetings.alert.end.description"="您尝试加入的会议已结束。您仍然可以查看会议聊天记录。"; /* Contacts selection screen header: Invite contacts to meetings */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="您之前已通过Google Play或华为应用商店订阅了Pro会员方案。请在您设备上的Google Play或华为应用商店中手动取消您的订阅后进行重试。"; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="您想要取消当前订阅并继续购买吗?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="不适用于您当前的会员方案"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="当前的订阅"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="更新"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="该文件将更新为新版本。"; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="适用于所有%@个重复文件"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="这里只有您一人"; /* Meetings end call dialog description */ @@ -3153,9 +3153,9 @@ /* Start conversation: Schedule meeting context menu */ "meetings.startConversation.contextMenu.scheduleMeeting"="安排会议"; /* Error message when trying to login and the account is blocked due to copyright violation */ -"account.suspension.message.copyright"="Your MEGA account has been suspended due to repeated allegations of copyright infringements. This means you cannot access your account or data within it.\n\nCheck your email for more information on how to file a counter-notice."; +"account.suspension.message.copyright"="由于多次指控侵犯版权,您的MEGA帐户已被暂停。这意味着您无法访问自己的帐户或其中的数据。\n\n请查看您的电子邮件,了解有关如何提交抗辩通知的更多信息。"; /* Error message when trying to login and the account is blocked due to any type of suspension, but copyright suspension */ -"account.suspension.message.nonCopyright"="Your account was terminated due to a breach of MEGA’s Terms of Service.\n\nYou will not be able to regain access to your stored data or be authorised to register a new MEGA account."; +"account.suspension.message.nonCopyright"="由于违反MEGA的服务条款,您的帐户已被终止。\n\n您将无法重新获得对存储数据的访问权限,也无法被授权注册新的MEGA账户。"; /* Text shown in extensions to notify users that they have to open the application first */ "extensions.OpenApp.Message"="打开MEGA应用程序并登录以继续。"; /* Account Storage title label of used storage size */ @@ -3175,21 +3175,21 @@ /* Chat Meetings tab - Section header title for the chat listing screen */ "chat.listing.sectionHeader.pastMeetings.title"="过去的会议"; /* Context menu item. Allows the user to verify contacts of the shared folder. The %@ placeholder will be replaced with the contact's name */ -"general.menuAction.verifyContact.title"="Approve %@"; +"general.menuAction.verifyContact.title"="验证%@"; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address that needs to be verified */ -"sharedItems.contactVerification.section.verifyContact.owner.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you share information with before they can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.owner.message"="我们采用零知识加密保护您的数据。为了进一步确保安全性,我们要求您对与您共享信息的联系人进行验证,然后他们才能访问共享文件夹。"; /* On fingerprint verification screen for Shared Items. Header message located at the top of "My credentials" section */ -"sharedItems.contactVerification.section.myCredentials.message"="To approve your contact, ensure the credentials you see above match their account credentials. You can ask them to share their credentials with you."; +"sharedItems.contactVerification.section.myCredentials.message"="要验证您的联系人,请确保您在上面看到的凭证与他们的帐户凭证相符。您可以要求他们与您共享他们的凭据。"; /* On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address if the owner of the received shared item is unverified */ -"sharedItems.contactVerification.section.verifyContact.receiver.message"="We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you receive information from before you can access the shared folders."; +"sharedItems.contactVerification.section.verifyContact.receiver.message"="我们采用零知识加密保护您的数据。为了进一步确保安全性,我们要求您在访问共享文件夹之前您你接收信息的联系人进行验证。"; /* On fingerprint verification screen for Incoming Shared Items. Yellow banner message that will be shown to the receiver if the owner of the folder haven't verified them yet. */ "sharedItems.contactVerification.section.verifyContact.bannerMessage"=""; /* On Cloud Drive screen for incoming shared folder, if the contact which shared the folder is not verified, this is the message that wwe display */ -"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@ is shared by a contact you haven’t approved. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"sharedItems.contactVerification.contactNotVerifiedBannerMessage"="%@是由您尚未验证的联系人共享。为确保额外的安全性,我们建议您通过点击要验证的联系人旁边的ⓘ来验证其在联系人中的凭证。"; /* Title of the label in verification screen. It shows the credentials of the current user so it can be used to be verified by other contacts */ "verifyCredentials.yourCredentials.title"="您的凭证"; /* Title of the header label in contact verification screen. */ -"verifyCredentials.headerMessage"="We protect your data with zero-knowledge encryption so all the information you store, share, and receive on MEGA is secure. To ensure extra security, we ask you to approve the contacts you share information with or receive data from."; +"verifyCredentials.headerMessage"="我们使用零知识加密来保护您的数据,因此您在MEGA上存储、共享和接收的所有信息都是安全的。为了确保额外的安全性,我们要求您验证与您共享信息,或从中接收数据的联系人。"; /* On Incoming Shared Items Tab. Name of the folder if the the owner and the receiver has not yet verified each other. */ "sharedItems.tab.incoming.undecryptedFolderName"="[未解密的文件夹]"; /* On Outgoing Shared Items Tab. Text that shows the receiver's name of the shared folder. %@ will be replaced with the receiver's name. */ @@ -3317,9 +3317,9 @@ /* Text description for a meeting showing that is a monthly recurring meeting */ "meetings.scheduled.recurring.monthly"="每月"; /* On Outgoing Shared Items Tab. Title of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ -"sharedItems.tab.outgoing.modal.cannotVerifyContact.title"="Cannot approve contact"; +"sharedItems.tab.outgoing.modal.cannotVerifyContact.title"="无法验证联系人"; /* On Outgoing Shared Items Tab. Message of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. */ -"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="You can’t approve %@ as they’re not in your contact list. Wait for them to accept your invitation first."; +"sharedItems.tab.outgoing.modal.cannotVerifyContact.message"="您无法验证%@,因为他们不在您的联系人列表中。请先等待他们接受您的邀请。"; /* Title shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled */ "picker.disable.passcode.title"="禁用密码"; /* Description shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled, it shows the users how to disable the passcode */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="已锁定"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="未知设备"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="在云盘中显示"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="结束重复"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3641,11 +3645,11 @@ /* Album failed to save to cloud drive alert message */ "albumLink.alert.message.albumFailedToSaveToCloudDrive"="“%@”无法保存到云盘。请稍后重试,如果问题仍然存在,请联系与您共享链接的人。"; /* Text displayed on banner we show when sharing folder with unverified contacts */ -"shareFolder.contactsNotVerified"="Some of the contacts you’re sharing information with haven’t been approved by you. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve."; +"shareFolder.contactsNotVerified"="某些您与之共享信息的联系人尚未经过您的验证。为确保额外的安全性,我们建议您点击待验证的联系人旁边的ⓘ来验证其在联系人中的凭据。"; /* Rename public album alert title */ "albumLink.alert.renameAlbum.title"="重命名相册"; /* Rename public album alert message */ -"albumLink.alert.renameAlbum.message"="An album named “%@” already exists in your Albums. Enter a new name for the album you’re saving."; +"albumLink.alert.renameAlbum.message"="您的相册中已存在一个名为“%@”的相册。请为您要保存的相册输入一个新名称。"; /* Button title of the dialog for transfer quota error that will direct to upgrade plan */ "transferQuotaError.button.upgrade"="升级"; /* Button title of the dialog for transfer quota error that will dismiss dialog */ @@ -3653,29 +3657,29 @@ /* Button title of the dialog for transfer quota error to buy new plan */ "transferQuotaError.button.buyNewPlan"="购买新的会员方案"; /* Footer message of the dialog for transfer quota error. [A] will be replaced by the usage quota percent. [B] will be replaced by the max transfer quota in TB. For example: 100% of 16 TB used */ -"transferQuotaError.footerMessage.quotaUsage"="[A]%% of [B] TB used"; +"transferQuotaError.footerMessage.quotaUsage"="[A]已使用[B] TB存储空间的%%"; /* Title of the dialog for transfer quota error with limited available trasfer quota */ "transferQuotaError.downloadLimitedQuota.title"="可用传输流量有限"; /* Message text of the dialog for transfer quota error with limited available trasfer quota for free accounts */ -"transferQuotaError.downloadLimitedQuota.freeAccount.message"="Downloading may be interrupted as you’ve used most of your transfer quota for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota."; +"transferQuotaError.downloadLimitedQuota.freeAccount.message"="因为您已使用此IP地址的大部分传输流量,下载可能会中断。要获得更多流量,请升级到Pro方案或等待%@直到您的IP地址有更多免费流量可用。[A]了解更多[/A]关于传输流量。"; /* Message text of the dialog for transfer quota error with limited available trasfer quota for pro accounts */ -"transferQuotaError.downloadLimitedQuota.proAccount.message"="Downloading may be interrupted as you’ve used most of your transfer quota on this account. To get more quota, purchase a new plan, or if you have a recurring subscription with MEGA, you can wait for your plan to renew."; +"transferQuotaError.downloadLimitedQuota.proAccount.message"="由于您已使用此帐户的大部分传输流量,下载可能会中断。要获得更多流量,请购买新会员方案,或者如果您有MEGA定期订阅,您可以等待方案续订。"; /* Title of the dialog for transfer quota error with exceeded quota */ "transferQuotaError.downloadExceededQuota.title"="已超出传输流量上限"; /* Message text of the dialog for transfer quota error with exceeded download trasfer quota for free accounts */ -"transferQuotaError.downloadExceededQuota.freeAccount.message"="You can’t continue downloading as you don’t have any transfer quota left for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota."; +"transferQuotaError.downloadExceededQuota.freeAccount.message"="%@因为此IP地址的传输流量不足,您无法继续下载。要获得更多流量,请升级到Pro方案或等待直到您的IP地址有更多免费流量可用。[A]了解更多[/A]关于传输流量。"; /* Message text of the dialog for transfer quota error with exceeded download trasfer quota for pro accounts */ -"transferQuotaError.downloadExceededQuota.proAccount.message"="You can’t continue downloading as you don’t have any transfer quota left on this account. To get more quota, purchase a new plan, or if you have a recurring subscription with MEGA, you can wait for your plan to renew."; +"transferQuotaError.downloadExceededQuota.proAccount.message"="因为此帐户上的传输流量不足,您无法继续下载。要获得更多流量,请购买新的会员方案,或者如果您有MEGA的定期订阅,您可以等待会员方案续订。"; /* Message text of the dialog for transfer quota error with exceeded streaming trasfer quota for free accounts */ -"transferQuotaError.streamingExceededQuota.freeAccount.message"="Your media stopped playing as you don’t have any transfer quota left for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota."; +"transferQuotaError.streamingExceededQuota.freeAccount.message"="您的媒体已停止播放,因为此IP地址的传输流量不足。要获得更多流量,请升级到Pro方案或等待%@直到您的IP地址有更多免费流量可用。[A]了解更多[/A]关于传输流量。"; /* Message text of the dialog for transfer quota error with exceeded download trasfer quota for pro accounts */ -"transferQuotaError.streamingExceededQuota.proAccount.message"="Your media stopped playing as you don’t have any transfer quota left on this account. To get more quota, purchase a new plan, or if you have a recurring subscription with MEGA, you can wait for your plan to renew."; +"transferQuotaError.streamingExceededQuota.proAccount.message"="因为您的帐户上的传输流量不足,您的媒体已停止播放。要获得更多流量,请购买新的会员方案,或者如果您有MEGA的定期订阅,您可以等待会员方案续订。"; /* Meeting waiting room leave button */ "meetings.waitingRoom.leave"="离开"; /* Meeting waiting room leave alert message */ "meetings.waitingRoom.alert.leaveMeeting"="离开会议吗?"; /* Meeting waiting room don't leave button */ -"meetings.waitingRoom.alert.dontLeave"="Don’t leave"; +"meetings.waitingRoom.alert.dontLeave"="不要离开"; /* Meeting waiting room guest join first name textfield */ "meetings.waitingRoom.guest.firstName"="名"; /* Meeting waiting room guest join last name textfield */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="加入"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="Wait for host to let you in"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="等着主持人让您进入"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="等待主持人开始会议"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="主持人尚未请您进入"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="好的,明白了"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@正在等待加入通话"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="拒绝"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="允许"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="查看等候室"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="允许所有请求"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="是否拒绝%@加入?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="拒绝加入"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="取消"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@正在等待加入“%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="在MEGA中打开"; \ No newline at end of file diff --git a/iMEGA/Languages/zh-Hans.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hans.lproj/Localizable.stringsdict similarity index 98% rename from iMEGA/Languages/zh-Hans.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hans.lproj/Localizable.stringsdict index 4cd1fe4bac..8df83ca936 100644 --- a/iMEGA/Languages/zh-Hans.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hans.lproj/Localizable.stringsdict @@ -2252,10 +2252,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Meeting will occur every month on day [cardinalDay]. other - Meeting will occur every %d months on day [cardinalDay]. + 会议将在每%d个月的[cardinalDay]日举行。 meetings.scheduled.create.monthly.multipleDaysCardinal.footerNote @@ -2268,10 +2266,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Meeting will occur every month on day [cardinalDays] and day [cardinalLastDay]. other - Meeting will occur every %d months on day [cardinalDays] and day [cardinalLastDay]. + 会议将在每%d个月的[cardinalDays]日和[cardinalLastDay]日举行。 meetings.scheduled.create.monthly.weekNumberAndWeekDay.footerNote @@ -2326,10 +2322,8 @@ NSStringPluralRuleType NSStringFormatValueTypeKey d - one - Every month on day [cardinalNumber] other - Every %d months on day [cardinalNumber] + 每%d个月的[cardinalNumber]日 meetings.scheduled.create.monthly.weekNumberAndWeekDay.selectedFrequency @@ -2374,5 +2368,35 @@ %d个文件已保存到云盘 + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + %d个下载已开始 + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/iMEGA/Languages/zh-Hant.lproj/Localizable.strings b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hant.lproj/Localizable.strings similarity index 98% rename from iMEGA/Languages/zh-Hant.lproj/Localizable.strings rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hant.lproj/Localizable.strings index 5cff7078c2..03deab309c 100644 --- a/iMEGA/Languages/zh-Hant.lproj/Localizable.strings +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hant.lproj/Localizable.strings @@ -855,7 +855,7 @@ /* Section title where you can 'Clear Offline files' of your MEGA app */ "clearOfflineFiles"="清除可離線使用的檔案"; /* Question shown after you tap on 'Settings' - 'File Management' - 'Clear Offline files' to confirm the action */ -"settings.fileManagement.alert.clearAllOfflineFiles"="清除所有離線使用的檔案?"; +"settings.fileManagement.alert.clearAllOfflineFiles"="清除所有可離線使用的檔案?"; /* Section title where you can 'Clear Cache' of your MEGA app */ "clearCache"="清除快取"; /* "Title header that refers to where do you do the action 'Empty Rubbish Bin' inside 'Settings' -> 'Advanced' section" */ @@ -2710,6 +2710,8 @@ "account.upgrade.alreadyHaveASubscription.message"="您之前已透過Google Play或華為的應用程式商店訂閱Pro方案。請在您裝置上的Google Play或華為應用程式商店中手動取消您的訂閱,然後重試一遍。"; /* Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. */ "account.upgrade.alreadyHaveACancellableSubscription.message"="您要取消目前的訂閱並繼續購買嗎?"; +/* Error message shown when a user tries to purchase plan that is not available with their current plan */ +"account.upgrade.notAvailableWithCurrentPlan.message"="不適用於您目前的方案"; /* Dialog title shown before deleting an account with active subscription */ "account.delete.subscription.title"="現行的訂閱"; /* Dialog message shown before deleting an account with active iTunes subscription */ @@ -3054,8 +3056,6 @@ "nameCollision.files.action.update.title"="更新"; /* Text description for the update action when upload and already exists in destination */ "nameCollision.files.action.update.description"="檔案將更新為新版本。"; -/* Text to indicate the user that action selected will be applied to all duplicated transfers */ -"nameCollision.applyToAll"="套用至所有%@個重複的項目"; /* Meetings end call dialog title */ "meetings.endCallDialog.title"="這裡只有你一人"; /* Meetings end call dialog description */ @@ -3067,7 +3067,7 @@ /* Meetings: Notification message to be shown to the user when the timer is set and call is going to end after the timer duration */ "meetings.notification.EndCallTimerDuration"="通話將於%@後結束"; /* Text for the action of downloading an item to the offline section */ -"general.downloadToOffline"="下載為離線使用"; +"general.downloadToOffline"="設為可離線使用"; /* Text for the popup displayed when you try to download file to photos while file is already being downloaded */ "general.fileIsBeingDownloaded"="檔案已在下載中"; /* Button and title text that ends the meeting for all */ @@ -3442,6 +3442,10 @@ "device.center.backup.blocked.status.message"="已封鎖"; /* Default device name in case the device name cannot be obtained */ "device.center.default.device.title"="不明的裝置"; +/* Show in Cloud Drive section action title */ +"device.center.show.in.cloud.drive.action.title"="在雲端硬碟中顯示"; +/* Show in Backups section action title */ +"device.center.show.in.backups.action.title"="Show in Backups"; /* End Recurrence option title in creating schedule meeting screen. */ "meetings.scheduleMeeting.create.endRecurrence.title"="結束重複"; /* End Recurrence on date option title in creating schedule meeting screen. */ @@ -3683,4 +3687,36 @@ /* Meeting waiting room guest join button */ "meetings.waitingRoom.guest.join"="加入"; /* Meeting waiting room message of wait for host to let you in */ -"meetings.waitingRoom.message.waitForHostToLetYouIn"="等待主持人讓你進入"; \ No newline at end of file +"meetings.waitingRoom.message.waitForHostToLetYouIn"="等待主持人讓你進入"; +/* Meeting waiting room message of wait for host to start the meeting */ +"meetings.waitingRoom.message.waitForHostToStartTheMeeting"="等待主持人開始會議"; +/* Meeting waiting room alert title of Host didn’t let you in */ +"meetings.waitingRoom.alert.hostDidNotLetYouIn"="主持人沒有讓您進入"; +/* Meeting waiting room alert message of You'll be removed from the waiting room */ +"meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom"="You’ll be removed from the waiting room"; +/* Meeting waiting room alert action of ok got it */ +"meetings.waitingRoom.alert.okGotIt"="好的,明白了"; +/* Call waiting room alert message text to notify host that someone is waiting to join */ +"chat.call.waitingRoom.alert.message.one"="%@ is waiting to join the call"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.deny"="拒絕"; +/* Call waiting room alert button to admit access */ +"chat.call.waitingRoom.alert.button.admit"="允許"; +/* Call waiting room alert message text to notify host that several participants are waiting to join */ +"chat.call.waitingRoom.alert.message.several"=""; +/* Call waiting room alert button to open waiting room UI */ +"chat.call.waitingRoom.alert.button.seeWaitingRoom"="查看等候室"; +/* Call waiting room alert button to allow access to everyone */ +"chat.call.waitingRoom.alert.button.admitAll"="允許全部"; +/* Call waiting room alert message text to confirm deny access from host */ +"chat.call.waitingRoom.alert.message.denyAcces"="Deny %@ entry?"; +/* Call waiting room alert button to deny access */ +"chat.call.waitingRoom.alert.button.confirmDeny"="拒絕進入"; +/* Call waiting room alert button to allow access */ +"chat.call.waitingRoom.alert.button.cancel"="取消"; +/* Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.one"="%@ is waiting to join “%@”"; +/* Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) */ +"chat.call.waitingRoom.alert.outsideCallUI.message.several"=""; +/* Context menu action seen from native files application when selecting a file that's also present on MEGA */ +"fileProviderExtension.action.open"="Open in MEGA"; \ No newline at end of file diff --git a/iMEGA/Languages/zh-Hant.lproj/Localizable.stringsdict b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hant.lproj/Localizable.stringsdict similarity index 98% rename from iMEGA/Languages/zh-Hant.lproj/Localizable.stringsdict rename to Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hant.lproj/Localizable.stringsdict index e86388a36b..b43755f721 100644 --- a/iMEGA/Languages/zh-Hant.lproj/Localizable.stringsdict +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/zh-Hant.lproj/Localizable.stringsdict @@ -2368,5 +2368,37 @@ %d個檔案已儲存到雲端硬碟 + general.saveToPhotos.started + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Download started + other + %d downloads started + + + general.format.itemsSelected + + NSStringLocalizedFormatKey + %#@count@ + count + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item selected + other + %d items selected + + \ No newline at end of file diff --git a/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Strings+Generated.swift b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Strings+Generated.swift new file mode 100644 index 0000000000..fafa1cbafa --- /dev/null +++ b/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Strings+Generated.swift @@ -0,0 +1,6192 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return prefer_self_in_static_references + +// MARK: - Strings + +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +public enum Strings { + public enum Localizable { + /// Shows number of the current page. '%1' will be replaced by current page number. '%2' will be replaced by number of all pages. + public static let _1Of2 = Strings.tr("Localizable", "%1 of %2", fallback: "%1 of %2") + /// Management message shown in a chat when the user %@ creates a public link for the chat + public static func createdAPublicLinkForTheChat(_ p1: Any) -> String { + return Strings.tr("Localizable", "%@ created a public link for the chat.", String(describing: p1), fallback: "%@ created a chat link") + } + /// Management message shown in a chat when the user %@ enables the 'Encrypted Key Rotation' + public static func enabledEncryptedKeyRotation(_ p1: Any) -> String { + return Strings.tr("Localizable", "%@ enabled Encrypted Key Rotation", String(describing: p1), fallback: "%@ enabled encryption key rotation.") + } + /// Message to inform the local user that someone has joined the current group call + public static func joinedTheCall(_ p1: Any) -> String { + return Strings.tr("Localizable", "%@ joined the call.", String(describing: p1), fallback: "%@ joined the call.") + } + /// Management message shown in a chat when the user %@ joined it from a public chat link + public static func joinedTheGroupChat(_ p1: Any) -> String { + return Strings.tr("Localizable", "%@ joined the group chat.", String(describing: p1), fallback: "%@ joined the group chat.") + } + /// Message to inform the local user that someone has left the current group call + public static func leftTheCall(_ p1: Any) -> String { + return Strings.tr("Localizable", "%@ left the call.", String(describing: p1), fallback: "%@ left the call") + } + /// Management message shown in a chat when the user %@ removes a public link for the chat + public static func removedAPublicLinkForTheChat(_ p1: Any) -> String { + return Strings.tr("Localizable", "%@ removed a public link for the chat.", String(describing: p1), fallback: "%@ removed the chat link") + } + /// Over Disk Quota of number of days + public static func dDays(_ p1: Int) -> String { + return Strings.tr("Localizable", "%d days", p1, fallback: "%d days") + } + /// Singular of participant. 1 participant + public static func dParticipant(_ p1: Int) -> String { + return Strings.tr("Localizable", "%d participant", p1, fallback: "%d participant") + } + /// Plural of participant. 2 participants + public static func dParticipants(_ p1: Int) -> String { + return Strings.tr("Localizable", "%d participants", p1, fallback: "%d participants") + } + /// A hint to show one option is recommended by MEGA + public static let recommended = Strings.tr("Localizable", "(Recommended)", fallback: "(Recommended)") + /// String shown when multi selection is enabled and only one item has been selected. + public static let _1Selected = Strings.tr("Localizable", "1 selected", fallback: "1 selected") + /// Chat Notifications DND: Option that deactivates DND after 24 hours + public static let _24Hours = Strings.tr("Localizable", "24 hours", fallback: "24 hours") + /// Chat Notifications DND: Option that deactivates DND after 30 minutes + public static let _30Minutes = Strings.tr("Localizable", "30 minutes", fallback: "30 minutes") + /// Action text to change to 4-Digit Numeric passcode type. + public static let _4DigitNumericCode = Strings.tr("Localizable", "4-Digit Numeric Code", fallback: "4-digit numeric code") + /// Chat Notifications DND: Option that deactivates DND after 6 hours + public static let _6Hours = Strings.tr("Localizable", "6 hours", fallback: "6 hours") + /// Action text to change to 6-Digit Numeric passcode type. + public static let _6DigitNumericCode = Strings.tr("Localizable", "6-Digit Numeric Code", fallback: "6-digit numeric code") + /// Text shown in the chat toolbar while the user is recording a voice clip. The < character should be > in RTL languages. + public static let slideToCancel = Strings.tr("Localizable", "< Slide to cancel", fallback: "< Slide to cancel") + /// Text indicating the user next step to unlock suspended account. Please leave [S], [/S] as it is which is used to bolden the text. + public static let sPleaseVerifyYourEmailSAndFollowItsStepsToUnlockYourAccount = Strings.tr("Localizable", "[S]Please verify your email[/S] and follow its steps to unlock your account.", fallback: "Please follow the steps in the [S]verification email[/S] to unlock your account.") + /// notification text + public static let aUserHasLeftTheSharedFolder0 = Strings.tr("Localizable", "A user has left the shared folder {0}", fallback: "A user has left the shared folder {0}") + /// Title of one of the Settings sections where you can see things 'About' the app + public static let about = Strings.tr("Localizable", "about", fallback: "About") + /// When somebody accepted your contact request + public static let acceptedYourContactRequest = Strings.tr("Localizable", "Accepted your contact request", fallback: "Accepted your contact request") + /// Label to show that an error related with an denied access occurs during a SDK operation. + public static let accessDenied = Strings.tr("Localizable", "Access denied", fallback: "Access denied") + /// SDK error returned for operations only allowed for business master users + public static let accessDeniedForUsers = Strings.tr("Localizable", "Access denied for users", fallback: "Access denied for non-admin users") + /// A description of MEGA features available only with a Pro plan. + public static let accessProOnlyFeaturesLikeSettingPasswordProtectionAndExpiryDatesForPublicFiles = Strings.tr("Localizable", "Access Pro only features like setting password protection and expiry dates for public files.", fallback: "Access Pro only features like setting password protection and expiry dates for public links.") + /// 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). + public static let accessToFoldersWasRemoved = Strings.tr("Localizable", "Access to folders was removed.", fallback: "Access to folders was removed.") + /// A notification telling the user that one of their contact’s accounts has been deleted or deactivated. + public static let accountHasBeenDeletedDeactivated = Strings.tr("Localizable", "Account has been deleted/deactivated", fallback: "Account has been deleted or deactivated") + /// Message shown when the user clicks on a confirm account link that has already been used + public static let accountAlreadyConfirmed = Strings.tr("Localizable", "accountAlreadyConfirmed", fallback: "Your account has been activated. Please log in.") + /// Error message when trying to login and the account is blocked + public static let accountBlocked = Strings.tr("Localizable", "accountBlocked", fallback: "Your account was terminated due to a breach of MEGA’s Terms of Service including, but not limited to, clause 15.") + /// During account cancellation (deletion) + public static let accountCanceledSuccessfully = Strings.tr("Localizable", "accountCanceledSuccessfully", fallback: "Your account has been deleted") + /// Text shown just after creating an account to remenber the user what to do to complete the account creation proccess + public static let accountNotConfirmed = Strings.tr("Localizable", "accountNotConfirmed", fallback: "Please check your email and follow the link to confirm your account.") + /// title of the My Account screen + public static let accountType = Strings.tr("Localizable", "accountType", fallback: "Account type:") + /// Label to give feedback to the user when he is going to share his location, indicating that it may not be the exact location. + public static func accurateToDMeters(_ p1: Int) -> String { + return Strings.tr("Localizable", "Accurate to %d meters", p1, fallback: "Accurate to %d meters") + } + /// Title of the Achievements section + public static let achievementsTitle = Strings.tr("Localizable", "achievementsTitle", fallback: "Achievements") + /// Title for the section "Acknowledgements" of the app + public static let acknowledgements = Strings.tr("Localizable", "acknowledgements", fallback: "Acknowledgements") + /// Business account status + public static let active = Strings.tr("Localizable", "Active", fallback: "Active") + /// Description shown in a page of the onboarding screens explaining contacts + public static let addContactsCreateANetworkColaborateMakeVoiceAndVideoCallsWithoutEverLeavingMEGA = Strings.tr("Localizable", "Add contacts, create a network, colaborate, make voice and video calls without ever leaving MEGA", fallback: "Add contacts, create a network, collaborate, and make voice and video calls without ever leaving MEGA") + /// Add Phone Number title + public static let addPhoneNumber = Strings.tr("Localizable", "Add Phone Number", fallback: "Add phone number") + /// Add your phone number title + public static let addYourPhoneNumber = Strings.tr("Localizable", "Add Your Phone Number", fallback: "Add your phone number") + /// Alert title shown when you select to add a contact inserting his/her email + public static let addContact = Strings.tr("Localizable", "addContact", fallback: "Add contact") + /// Button title to 'Add' the contact to your contacts list + public static let addContactButton = Strings.tr("Localizable", "addContactButton", fallback: "Add") + /// Button title shown in empty views when you can 'Add contacts' + public static let addContacts = Strings.tr("Localizable", "addContacts", fallback: "Add contacts") + /// A label for any ‘Added’ text or title. For example to show the upload date of a file/folder. + public static let added = Strings.tr("Localizable", "Added", fallback: "Added") + /// Content of a notification that informs how many files and folders have been added to a shared folder + public static func addedLldFiles(_ p1: Int) -> String { + return Strings.tr("Localizable", "Added %lld files", p1, fallback: "Added %lld files") + } + /// Content of a notification that informs how many files and folders have been added to a shared folder + public static func addedLldFilesAnd1Folder(_ p1: Int) -> String { + return Strings.tr("Localizable", "Added %lld files and 1 folder", p1, fallback: "Added %lld files and 1 folder") + } + /// Content of a notification that informs how many files and folders have been added to a shared folder + public static func addedLldFolders(_ p1: Int) -> String { + return Strings.tr("Localizable", "Added %lld folders", p1, fallback: "Added %lld folders") + } + /// Content of a notification that informs how many files and folders have been added to a shared folder + public static let added1File = Strings.tr("Localizable", "Added 1 file", fallback: "Added 1 file") + /// Content of a notification that informs how many files and folders have been added to a shared folder + public static func added1FileAndLldFolders(_ p1: Int) -> String { + return Strings.tr("Localizable", "Added 1 file and %lld folders", p1, fallback: "Added 1 file and %lld folders") + } + /// Content of a notification that informs how many files and folders have been added to a shared folder + public static let added1FileAnd1Folder = Strings.tr("Localizable", "Added 1 file and 1 folder", fallback: "Added 1 file and 1 folder") + /// Content of a notification that informs how many files and folders have been added to a shared folder + public static let added1Folder = Strings.tr("Localizable", "Added 1 folder", fallback: "Added 1 folder") + /// Content of a notification that informs how many files and folders have been added to a shared folder + public static let addedAFilesAndBFolders = Strings.tr("Localizable", "Added [A] files and [B] folders", fallback: "Added [A] files and [B] folders") + /// Button title shown in empty views when you can 'Add files' + public static let addFiles = Strings.tr("Localizable", "addFiles", fallback: "Add files") + /// Item menu option to add a contact through your device app Contacts + public static let addFromContacts = Strings.tr("Localizable", "addFromContacts", fallback: "Add from contacts") + /// Item menu option to add a contact writting his/her email + public static let addFromEmail = Strings.tr("Localizable", "addFromEmail", fallback: "Enter an email address") + /// Button label. Allows to add contacts in current chat conversation. + public static let addParticipant = Strings.tr("Localizable", "addParticipant", fallback: "Add participant…") + /// Menu item to add participants to a chat + public static let addParticipants = Strings.tr("Localizable", "addParticipants", fallback: "Add participants") + /// + public static let administrator = Strings.tr("Localizable", "Administrator", fallback: "Administrator") + /// Title of one of the Settings sections where you can configure 'Advanced' options + public static let advanced = Strings.tr("Localizable", "advanced", fallback: "Advanced") + /// Over Disk Quota warnig message to tell user your data is subject to deletion. + public static let afterThatYourDataIsSubjectToDeletion = Strings.tr("Localizable", "After that, your data is subject to deletion.", fallback: "After that, your data is subject to deletion.") + /// button caption text that the user clicks when he agrees + public static let agree = Strings.tr("Localizable", "agree", fallback: "Agree") + /// This text is displayed in the account creation screen with a checkbox. User will have to tick the checkbox before proceeding on to create an account + public static let agreeWithLosingPasswordYouLoseData = Strings.tr("Localizable", "agreeWithLosingPasswordYouLoseData", fallback: "I understand that [S]if I lose my password, I may lose my data.[/S] Read more about MEGA’s end-to-end encryption.") + /// + public static let agreeWithTheMEGATermsOfService = Strings.tr("Localizable", "agreeWithTheMEGATermsOfService", fallback: "I agree with the MEGA Terms of Service") + /// Title for the album/collection link view + public static let albumLink = Strings.tr("Localizable", "albumLink", fallback: "Album link") + /// Used in Photos app browser album listing screen. + public static let albums = Strings.tr("Localizable", "Albums", fallback: "Albums") + /// Add nickname screen: This text appears above the alias(nickname) entry + public static let aliasNickname = Strings.tr("Localizable", "Alias/ Nickname", fallback: "Alias or nickname") + /// Title of one of the filters in the Transfers section. In this case "All" transfers. + public static let all = Strings.tr("Localizable", "all", fallback: "All") + /// Text shown for switching from preview view to all media view. + public static let allMedia = Strings.tr("Localizable", "All Media", fallback: "All media") + /// Footer description when upload all burst photos is enabled + public static let allThePhotosFromYourBurstPhotoSequencesWillBeUploaded = Strings.tr("Localizable", "All the photos from your burst photo sequences will be uploaded.", fallback: "All the photos from your burst photo sequences will be uploaded.") + /// Button which triggers a request for a specific permission, that have been explained to the user beforehand + public static let allowAccess = Strings.tr("Localizable", "Allow Access", fallback: "Allow access") + /// Title label that explains that the user is going to be asked for the photos permission + public static let allowAccessToPhotos = Strings.tr("Localizable", "Allow Access to Photos", fallback: "Allow access to photos") + /// Footer text to explain the meaning of the functionaly 'Last seen' of your chat status. + public static let allowYourContactsToSeeTheLastTimeYouWereActiveOnMEGA = Strings.tr("Localizable", "Allow your contacts to see the last time you were active on MEGA.", fallback: "Allow your contacts to see the last time you were active on MEGA.") + /// Alert message to remenber the user that needs to enable purchases before continue + public static let allowPurchaseMessage = Strings.tr("Localizable", "allowPurchase_message", fallback: "You must enable In-App Purchases in your iOS Settings before restoring a purchase.") + /// Alert title to remenber the user that needs to enable purchases + public static let allowPurchaseTitle = Strings.tr("Localizable", "allowPurchase_title", fallback: "Allow purchases") + /// Label to show that an error related with an existent resource occurs during a SDK operation. + public static let alreadyExists = Strings.tr("Localizable", "Already exists", fallback: "Already exists") + /// Error message displayed when trying to invite a contact who is already added. + public static func alreadyAContact(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "alreadyAContact", p1, fallback: "%s is already a contact.") + } + /// 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. + public static let alwaysAllow = Strings.tr("Localizable", "alwaysAllow", fallback: "Always allow") + /// Section title inside of Settings - Appearance, where you can change the app's icon. + public static let appIcon = Strings.tr("Localizable", "App Icon", fallback: "App icon") + /// App means “Application” + public static let appVersion = Strings.tr("Localizable", "App Version", fallback: "App version") + /// Error message shown the In App Purchase is disabled in the device Settings + public static let appPurchaseDisabled = Strings.tr("Localizable", "appPurchaseDisabled", fallback: "In-app purchases is disabled, please enable it in the iOS Settings app") + /// Button title + public static let approve = Strings.tr("Localizable", "approve", fallback: "Approve") + /// Title of button to archive chats. + public static let archiveChat = Strings.tr("Localizable", "archiveChat", fallback: "Archive") + /// Confirmation message on archive chat dialog for user to confirm. + public static let archiveChatMessage = Strings.tr("Localizable", "archiveChatMessage", fallback: "Are you sure you want to archive this conversation?") + /// Title of flag of archived chats. + public static let archived = Strings.tr("Localizable", "archived", fallback: "Archived") + /// Title of archived chats button + public static let archivedChats = Strings.tr("Localizable", "archivedChats", fallback: "Archived") + /// text of the alert dialog when the user is changing the API URL to staging + public static let areYouSureYouWantToChangeToATestServerYourAccountMaySufferIrrecoverableProblems = Strings.tr("Localizable", "Are you sure you want to change to a test server? Your account may suffer irrecoverable problems", fallback: "Are you sure you want to change to a test server? Your account may suffer irrecoverable problems.") + /// Message when to move Camera Uploads folder to Rubbish Bin + public static let areYouSureYouWantToMoveCameraUploadsFolderToRubbishBinIfSoANewFolderWillBeAutoGeneratedForCameraUploads = Strings.tr("Localizable", "Are you sure you want to move Camera Uploads folder to Rubbish Bin? If so, a new folder will be auto-generated for Camera Uploads.", fallback: "Are you sure you want to move the Camera uploads folder to the Rubbish bin? If so, a new folder will be automatically generated for Camera uploads.") + /// Asking whether the user really wants to abort/stop the registration process or continue on. + public static let areYouSureYouWantToAbortTheRegistration = Strings.tr("Localizable", "areYouSureYouWantToAbortTheRegistration", fallback: "Are you sure you want to abort the registration?") + /// Mail subject to upgrade to a custom plan + public static let askUsHowYouCanUpgradeToACustomPlan = Strings.tr("Localizable", "Ask us how you can upgrade to a custom plan:", fallback: "Ask us how you can upgrade to a custom plan:") + /// A message appearing in the chat summary window when the most recent action performed by a user was attaching a file. Please keep %s as it will be replaced at runtime with the name of the attached file. + public static func attachedFile(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "attachedFile", p1, fallback: "Attached: %s") + } + /// 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. + public static func attachedXFiles(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "attachedXFiles", p1, fallback: "Attached %s files.") + } + /// Alert title to attract attention + public static let attention = Strings.tr("Localizable", "attention", fallback: "Attention") + /// Title for audio explorer view + public static let audio = Strings.tr("Localizable", "Audio", fallback: "Audio") + /// Alert title shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device + public static let authenticatorAppRequired = Strings.tr("Localizable", "Authenticator app required", fallback: "Authenticator app required") + /// 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. + public static let authenticityExplanation = Strings.tr("Localizable", "authenticityExplanation", fallback: "[S]Authenticity.[/S] The system ensures that the data received is from the sender displayed, and its content has not been manipulated during transit.") + /// Label for the setting that allow users to automatically add contacts when they scan his/her QR code. String as short as possible. + public static let autoAccept = Strings.tr("Localizable", "autoAccept", fallback: "Auto-accept") + /// Footer that explains the way Auto-Accept works for QR codes + public static let autoAcceptFooter = Strings.tr("Localizable", "autoAcceptFooter", fallback: "MEGA users who scan your QR code will be automatically added to your contact list.") + /// A header in the Chat settings. This changes the user's status to Away after some time of inactivity. + public static let autoAway = Strings.tr("Localizable", "autoAway", fallback: "Auto-away") + /// Text shown to explain what means 'Enable Camera Uploads'. The 'Cloud Drive' is the MEGA section, so please keep consistency + public static let automaticallyBackupYourPhotosAndVideosToTheCloudDrive = Strings.tr("Localizable", "Automatically backup your photos and videos to the Cloud Drive.", fallback: "Automatically back up your photos and videos to your Cloud drive.") + /// Text show under the setting 'History Retention' to explain what will happen if enabled + public static let automaticallyDeleteMessagesOlderThanACertainAmountOfTime = Strings.tr("Localizable", "Automatically delete messages older than a certain amount of time", fallback: "Automatically delete messages older than a certain amount of time.") + /// Text show under the setting 'History Retention' to explain that it is configured to '1 day' + public static let automaticallyDeleteMessagesOlderThanOneDay = Strings.tr("Localizable", "Automatically delete messages older than one day", fallback: "Automatically delete messages older than one day.") + /// Text show under the setting 'History Retention' to explain that it is configured to '1 month' + public static let automaticallyDeleteMessagesOlderThanOneMonth = Strings.tr("Localizable", "Automatically delete messages older than one month", fallback: "Automatically delete messages older than one month.") + /// Text show under the setting 'History Retention' to explain that it is configured to '1 week' + public static let automaticallyDeleteMessagesOlderThanOneWeek = Strings.tr("Localizable", "Automatically delete messages older than one week", fallback: "Automatically delete messages older than one week.") + /// Describe how works auto-renewable subscriptions on the Apple Store + public static let autorenewableDescription = Strings.tr("Localizable", "autorenewableDescription", fallback: "Subscriptions are renewed automatically for successive subscription periods of the same duration and at the same price as the initial period chosen. You can switch off the automatic renewal of your MEGA Pro subscription no later than 24 hours before your next subscription payment is due via your iTunes account settings page. To manage your subscriptions, simply tap on the App Store icon on your mobile device, sign in with your Apple ID at the bottom of the page (if you haven’t already done so) and then tap View ID. You’ll be taken to your account page where you can scroll down to Manage App Subscriptions. From there, you can select your MEGA Pro subscription and view your scheduled renewal date, choose a different subscription package or toggle the on-off switch to off to disable the auto-renewal of your subscription.") + /// Title for MEGA "Available" space. + public static let availableLabel = Strings.tr("Localizable", "availableLabel", fallback: "Available") + /// Title shown just after doing some action that requires confirming the action by an email + public static let awaitingEmailConfirmation = Strings.tr("Localizable", "awaitingEmailConfirmation", fallback: "Awaiting email confirmation.") + /// + public static let away = Strings.tr("Localizable", "away", fallback: "Away") + /// Label for recovery key button + public static let backupRecoveryKey = Strings.tr("Localizable", "backupRecoveryKey", fallback: "Back up Recovery key") + /// Label to show that an error related with a bad session ID occurs during a SDK operation. + public static let badSessionID = Strings.tr("Localizable", "Bad session ID", fallback: "Bad session ID") + /// SDK error returned when there is a balance error + public static let balanceError = Strings.tr("Localizable", "Balance error", fallback: "Balance error") + /// Button title to start the setup of a feature. For example 'Begin Setup' for Two-Factor Authentication + public static let beginSetup = Strings.tr("Localizable", "beginSetup", fallback: "Begin setup") + /// SDK error returned when the billing failed + public static let billingFailed = Strings.tr("Localizable", "Billing failed", fallback: "Billing failed") + /// Label to show that an error related with a blocked account occurs during a SDK operation. + public static let blocked = Strings.tr("Localizable", "Blocked", fallback: "Blocked") + /// 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. + public static let blockedYouAsAContact = Strings.tr("Localizable", "Blocked you as a contact", fallback: "Blocked you as a contact") + /// A user can mark a folder or file with its own colour, in this case “Blue”. + public static let blue = Strings.tr("Localizable", "Blue", fallback: "Blue") + /// + public static let business = Strings.tr("Localizable", "Business", fallback: "Business") + /// SDK error returned when a business account has expired + public static let businessAccountHasExpired = Strings.tr("Localizable", "Business account has expired", fallback: "Account deactivated") + /// + public static let busy = Strings.tr("Localizable", "busy", fallback: "Busy") + /// Title of the button in the contact info screen to start an audio call + public static let call = Strings.tr("Localizable", "Call", fallback: "Audio") + /// Text to inform the user there is an active call and is participating + public static let callStarted = Strings.tr("Localizable", "Call Started", fallback: "Call started") + /// When an active call of user A with user B had ended + public static let callEnded = Strings.tr("Localizable", "callEnded", fallback: "Call ended.") + /// When an active call of user A with user B had failed + public static let callFailed = Strings.tr("Localizable", "callFailed", fallback: "Call failed") + /// Label shown when you call someone (outgoing call), before the call starts. + public static let calling = Strings.tr("Localizable", "calling...", fallback: "Calling…") + /// When an active call of user A with user B had cancelled + public static let callWasCancelled = Strings.tr("Localizable", "callWasCancelled", fallback: "Call was cancelled") + /// When an active call of user A with user B had not answered + public static let callWasNotAnswered = Strings.tr("Localizable", "callWasNotAnswered", fallback: "Call was not answered") + /// When an outgoing call of user A with user B had been rejected by user B + public static let callWasRejected = Strings.tr("Localizable", "callWasRejected", fallback: "Call was rejected") + /// Header title of Camera section on MEGA Settings > Advanced screen. + public static let camera = Strings.tr("Localizable", "Camera", fallback: "Camera") + /// 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 + public static let cameraPermissions = Strings.tr("Localizable", "cameraPermissions", fallback: "Please give MEGA permission to access your Camera in Settings") + /// Message shown when the camera uploads have been completed + public static let cameraUploadsComplete = Strings.tr("Localizable", "cameraUploadsComplete", fallback: "Camera uploads complete") + /// Success message shown when Camera Uploads has been enabled + public static let cameraUploadsEnabled = Strings.tr("Localizable", "cameraUploadsEnabled", fallback: "Camera uploads enabled") + /// Title of one of the Settings sections where you can set up the 'Camera Uploads' options + public static let cameraUploadsLabel = Strings.tr("Localizable", "cameraUploadsLabel", fallback: "Camera uploads") + /// Message shown while uploading files. Singular. + public static let cameraUploadsPendingFile = Strings.tr("Localizable", "cameraUploadsPendingFile", fallback: "Upload in progress, 1 file pending") + /// Message shown while uploading files. Plural. + public static func cameraUploadsPendingFiles(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploadsPendingFiles", p1, fallback: "Upload in progress, %lu files pending") + } + /// Button title to cancel something + public static let cancel = Strings.tr("Localizable", "cancel", fallback: "Cancel") + /// During account cancellation (deletion) + public static let cancellationLinkHasExpired = Strings.tr("Localizable", "cancellationLinkHasExpired", fallback: "Cancellation link has expired.") + /// Possible state of a transfer. When the transfer was cancelled + public static let cancelled = Strings.tr("Localizable", "Cancelled", fallback: "Cancelled") + /// 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. + public static let cancelledTheirContactRequest = Strings.tr("Localizable", "Cancelled their contact request", fallback: "Cancelled their contact request") + /// Message of the alert shown when you try to cancel transfers where {%@} = {ALL, UPLOAD or DOWNLOAD} + {tranfers} + public static let cancelTransfersText = Strings.tr("Localizable", "cancelTransfersText", fallback: "Do you want to cancel all transfers?") + /// Title of the alert shown when you try to cancel transfers + public static let cancelTransfersTitle = Strings.tr("Localizable", "cancelTransfersTitle", fallback: "Cancel transfers") + /// In 'My account', when user want to delete/remove/cancel account will click button named 'Cancel your account' + public static let cancelYourAccount = Strings.tr("Localizable", "cancelYourAccount", fallback: "Delete account") + /// Menu option from the `Add` section that allows the user to capture a video or a photo and upload it directly to MEGA. + public static let capturePhotoVideo = Strings.tr("Localizable", "capturePhotoVideo", fallback: "Capture") + /// The title of the alert dialog to change the email associated to an account. + public static let changeEmail = Strings.tr("Localizable", "Change Email", fallback: "Change email address") + /// Dialog title for the change launch tab screen + public static let changeLaunchTab = Strings.tr("Localizable", "Change Launch Tab", fallback: "Change launch tab") + /// Dialog button text for the change launch tab screen + public static let changeSetting = Strings.tr("Localizable", "Change Setting", fallback: "Change setting") + /// title of the alert dialog when the user is changing the API URL to staging + public static let changeToATestServer = Strings.tr("Localizable", "Change to a test server?", fallback: "Change to a test server?") + /// 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'. + public static let changedGroupChatNameTo = Strings.tr("Localizable", "changedGroupChatNameTo", fallback: "[A] changed the group chat name to “[B]”") + /// Button title that allows the user change his name + public static let changeName = Strings.tr("Localizable", "changeName", fallback: "Change name") + /// Section title where you can change the app's passcode + public static let changePasscodeLabel = Strings.tr("Localizable", "changePasscodeLabel", fallback: "Change passcode") + /// Section title where you can change your MEGA's password + public static let changePasswordLabel = Strings.tr("Localizable", "changePasswordLabel", fallback: "Change password") + /// Chat section header + public static let chat = Strings.tr("Localizable", "chat", fallback: "Chat") + /// Default title of an empty chat. + public static func chatCreatedOnS1(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "Chat created on %s1", p1, fallback: "Chat created on %s1") + } + /// Message show when the history of a chat has been successfully deleted. + public static let chatHistoryHasBeenCleared = Strings.tr("Localizable", "Chat History has Been Cleared", fallback: "Chat history has been cleared") + /// Label shown in a cell where you can enable a switch to get a chat link + public static let chatLink = Strings.tr("Localizable", "Chat Link", fallback: "Chat link") + /// Shown when an invalid/inexisting/not-available-anymore chat link is opened. + public static let chatLinkUnavailable = Strings.tr("Localizable", "Chat Link Unavailable", fallback: "Chat link unavailable") + /// Chat Notifications DND: This text will appear in the settings of every chat with the on/off switch + public static let chatNotifications = Strings.tr("Localizable", "Chat Notifications", fallback: "Chat notifications") + /// 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. + public static let chatIntroductionMessage = Strings.tr("Localizable", "chatIntroductionMessage", fallback: "MEGA protects your chat with zero-knowledge encryption providing essential safety assurances:") + /// Alert message shown when a chat does not exist + public static let chatNotFound = Strings.tr("Localizable", "chatNotFound", fallback: "Chat not found") + /// Title show above the name of the persons with whom you're chatting + public static let chattingWith = Strings.tr("Localizable", "chattingWith", fallback: "Chatting with") + /// Choose Your Region title + public static let chooseYourRegion = Strings.tr("Localizable", "Choose Your Region", fallback: "Choose your region") + /// Menu option from the `Add` section that allows the user to choose a photo or video to upload it to MEGA. + public static let choosePhotoVideo = Strings.tr("Localizable", "choosePhotoVideo", fallback: "Choose from Photos") + /// Header that help you with the upgrading process explaining that you have to choose one of the plans below to continue + public static let choosePlan = Strings.tr("Localizable", "choosePlan", fallback: "Choose one of the plans from below:") + /// + public static let chooseYourAccountType = Strings.tr("Localizable", "chooseYourAccountType", fallback: "Choose your account type") + /// Label to show that an error related with a circular linkage occurs during a SDK operation. + public static let circularLinkageDetected = Strings.tr("Localizable", "Circular linkage detected", fallback: "Circular linkage detected") + /// Button title to clear something + public static let clear = Strings.tr("Localizable", "clear", fallback: "Clear") + /// tool bar title used in transfer widget, allow user to clear all items in the list + public static let clearAll = Strings.tr("Localizable", "Clear All", fallback: "Clear All") + /// Title show on the sheet to configure default values of the 'History Retention' setting + public static let clearMessagesOlderThan = Strings.tr("Localizable", "Clear Messages Older Than", fallback: "Clear messages older than") + /// tool bar title used in transfer widget, allow user to clear the selected items in the list + public static let clearSelected = Strings.tr("Localizable", "Clear Selected", fallback: "Clear selected") + /// Section title where you can 'Clear Cache' of your MEGA app + public static let clearCache = Strings.tr("Localizable", "clearCache", fallback: "Clear cache") + /// A button title to delete the history of a chat. + public static let clearChatHistory = Strings.tr("Localizable", "clearChatHistory", fallback: "Clear chat history") + /// A log message in the chat conversation to tell the reader that a participant [A] cleared the history of the chat. For example, Alice cleared the chat history. + public static let clearedTheChatHistory = Strings.tr("Localizable", "clearedTheChatHistory", fallback: "[A] cleared the chat history.") + /// Section title where you can 'Clear Offline files' of your MEGA app + public static let clearOfflineFiles = Strings.tr("Localizable", "clearOfflineFiles", fallback: "Clear Offline files") + /// A confirmation message for a user to confirm that they want to clear the history of a chat. + public static let clearTheFullMessageHistory = Strings.tr("Localizable", "clearTheFullMessageHistory", fallback: "Are you sure you want to clear the full message history of this conversation?") + /// A button label. The button allows the user to close the conversation. + public static let close = Strings.tr("Localizable", "close", fallback: "Close") + /// Account closure, password check dialog when user click on closure email. + public static let closeAccount = Strings.tr("Localizable", "closeAccount", fallback: "Close account") + /// 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. + public static let closeOtherSessions = Strings.tr("Localizable", "closeOtherSessions", fallback: "Close other sessions") + /// Title of the Cloud Drive section + public static let cloudDrive = Strings.tr("Localizable", "cloudDrive", fallback: "Cloud drive") + /// Title shown when your Cloud Drive is empty, when you don't have any files. + public static let cloudDriveEmptyStateTitle = Strings.tr("Localizable", "cloudDriveEmptyState_title", fallback: "No files in your Cloud drive") + /// Title shown when your Rubbish Bin is empty. + public static let cloudDriveEmptyStateTitleRubbishBin = Strings.tr("Localizable", "cloudDriveEmptyState_titleRubbishBin", fallback: "Empty Rubbish bin") + /// Success text shown in a label when the user scans a valid QR. String as short as possible. + public static let codeScanned = Strings.tr("Localizable", "codeScanned", fallback: "Code scanned") + /// Title of one of the filters in the Transfers section. In this case "Completed" transfers. + public static let completed = Strings.tr("Localizable", "Completed", fallback: "Completed") + /// Label for the state of a transfer when is being completing - (String as short as possible). + public static let completing = Strings.tr("Localizable", "Completing...", fallback: "Completing…") + /// 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. + public static let confidentialityExplanation = Strings.tr("Localizable", "confidentialityExplanation", fallback: "[S]Confidentiality.[/S] Only the author and intended recipients are able to decipher and read the content.") + /// Footer text explaining what means choosing a sorting preference 'Per Folder' or 'Same for All' in Settings - Appearance - Sorting And View Mode. + public static let configureColumnSortingOrderOnAPerFolderBasisOrUseTheSameOrderForAllFolders = Strings.tr("Localizable", "Configure column sorting order on a per-folder basis, or use the same order for all folders.", fallback: "Configure column sorting order on a per-folder basis, or use the same order for all folders.") + /// Footer text to explain what you could do in the Settings - User Interface - Launch section. + public static let configureDefaultLaunchSection = Strings.tr("Localizable", "Configure default launch section.", fallback: "Configure default launch section.") + /// Footer text to explain what you could do in the Settings - Appearance - Sorting And View Mode section. + public static let configureSortingOrderAndTheDefaultViewListOrThumbnail = Strings.tr("Localizable", "Configure sorting order and the default view (List or Thumbnail).", fallback: "Configure sorting order and the default view (List or Thumbnail).") + /// Title text for the account confirmation. + public static let confirm = Strings.tr("Localizable", "confirm", fallback: "Confirm") + /// Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible). + public static let confirmAccount = Strings.tr("Localizable", "Confirm account", fallback: "Confirm account") + /// Button text for the user to confirm their change of email address. + public static let confirmEmail = Strings.tr("Localizable", "confirmEmail", fallback: "Confirm email") + /// Hint text where the user have to re-write the new email to confirm it + public static let confirmNewEmail = Strings.tr("Localizable", "confirmNewEmail", fallback: "Confirm new email") + /// Hint text where the user have to re-write the new password to confirm it + public static let confirmPassword = Strings.tr("Localizable", "confirmPassword", fallback: "Confirm password") + /// Text shown on the confirm account view to remind the user what to do + public static let confirmText = Strings.tr("Localizable", "confirmText", fallback: "Please enter your password to confirm your account") + /// The [X] will be replaced with the e-mail address. + public static let congratulationsNewEmailAddress = Strings.tr("Localizable", "congratulationsNewEmailAddress", fallback: "Congratulations, your new email address for this MEGA account is: [X]") + /// Label in login screen to inform about the chat initialization proccess + public static let connecting = Strings.tr("Localizable", "connecting", fallback: "Connecting…") + /// Label to show that an error related with too many connections occurs during a SDK operation. + public static let connectionOverflow = Strings.tr("Localizable", "Connection overflow", fallback: "Connection overflow") + /// Menu option from the `Add` section that allows the user to share contact. + public static let contact = Strings.tr("Localizable", "Contact", fallback: "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). + public static let contactRelationshipEstablished = Strings.tr("Localizable", "Contact relationship established", fallback: "Contact relationship established") + /// A dialog message which is shown to sub-users of expired business accounts. + public static let contactYourBusinessAccountAdministratorToResolveTheIssueAndActivateYourAccount = Strings.tr("Localizable", "Contact your business account administrator to resolve the issue and activate your account.", fallback: "Contact your Business account administrator to resolve the issue and activate your account.") + /// Label title above the fingerprint credentials of a user's contact. A credential in this case is a stored piece of information representing the identity of the contact + public static let contactCredentials = Strings.tr("Localizable", "contactCredentials", fallback: "Contact credentials") + /// Clue text to help the user know what should write there. In this case the contact email you want to add to your contacts list + public static let contactEmail = Strings.tr("Localizable", "contactEmail", fallback: "Contact email") + /// Notification text body shown when you have received a contact request + public static let contactRequest = Strings.tr("Localizable", "contactRequest", fallback: "Contact request") + /// Title of Contacts requests section + public static let contactRequests = Strings.tr("Localizable", "contactRequests", fallback: "Contact requests") + /// Title shown when the Contacts section is empty, when you have not added any contact. + public static let contactsEmptyStateTitle = Strings.tr("Localizable", "contactsEmptyState_title", fallback: "No contacts") + /// Title of the Contacts section + public static let contactsTitle = Strings.tr("Localizable", "contactsTitle", fallback: "Contacts") + /// Label for what a selection contains. For example: "Contains: 3 folders & 13 files". (no need to put the colon punctuation in the translation) + public static let contains = Strings.tr("Localizable", "contains", fallback: "Contains") + /// 'Next' button in a dialog + public static let `continue` = Strings.tr("Localizable", "continue", fallback: "Continue") + /// Text of the button after the links were copied to the clipboard + public static let copiedToTheClipboard = Strings.tr("Localizable", "copiedToTheClipboard", fallback: "Copied to the clipboard") + /// List option shown on the details of a file or folder + public static let copy = Strings.tr("Localizable", "copy", fallback: "Copy") + /// Caption of the button that will copy multiple files or folders export links to his clipboard + public static let copyAll = Strings.tr("Localizable", "Copy All", fallback: "Copy all") + /// Success message shown when you have copied 1 file and 1 folder + public static let copyFileFolderMessage = Strings.tr("Localizable", "copyFileFolderMessage", fallback: "1 file and 1 folder copied") + /// Success message shown when you have copied 1 file and {1+} folders + public static func copyFileFoldersMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "copyFileFoldersMessage", p1, fallback: "1 file and %d folders copied") + } + /// Success message shown when you have copied 1 file + public static let copyFileMessage = Strings.tr("Localizable", "copyFileMessage", fallback: "1 file copied") + /// Success message shown when you have copied {1+} files and 1 folder + public static func copyFilesFolderMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "copyFilesFolderMessage", p1, fallback: "%d files and 1 folder copied") + } + /// Success message shown when you have copied [A] = {1+} files and [B] = {1+} folders + public static let copyFilesFoldersMessage = Strings.tr("Localizable", "copyFilesFoldersMessage", fallback: "[A] files and [B] folders copied") + /// Success message shown when you have copied {1+} files + public static func copyFilesMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "copyFilesMessage", p1, fallback: "%d files copied") + } + /// Success message shown when you have copied 1 folder + public static let copyFolderMessage = Strings.tr("Localizable", "copyFolderMessage", fallback: "1 folder copied") + /// Success message shown when you have copied {1+} folders + public static func copyFoldersMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "copyFoldersMessage", p1, fallback: "%d folders copied") + } + /// Title for a button that copies the key of the link to the clipboard + public static let copyKey = Strings.tr("Localizable", "copyKey", fallback: "Copy key") + /// Title for a button to copy the link to the clipboard + public static let copyLink = Strings.tr("Localizable", "copyLink", fallback: "Copy link") + /// String already exists: 367, but we need to split paragraphs. + public static let copyrightMessagePart1 = Strings.tr("Localizable", "copyrightMessagePart1", fallback: "MEGA respects the copyrights of others and requires that users of the MEGA Cloud service comply with the laws of copyright.") + /// String already exists: 367, but we need to split paragraphs + public static let copyrightMessagePart2 = Strings.tr("Localizable", "copyrightMessagePart2", fallback: "You are strictly prohibited from using the MEGA Cloud service to infringe copyrights. You may not upload, download, store, share, display, stream, distribute, email, link to, transmit or otherwise make available any files, data or content that infringes any copyright or other proprietary rights of any person or entity.") + /// A title for the Copyright Warning dialog. + public static let copyrightWarning = Strings.tr("Localizable", "copyrightWarning", fallback: "Copyright warning") + /// 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. + public static let copyrightWarningToAll = Strings.tr("Localizable", "copyrightWarningToAll", fallback: "Copyright warning to all users") + /// Text shown when an error occurs when trying to save a photo or video to Photos app + public static let couldNotSaveItem = Strings.tr("Localizable", "Could not save Item", fallback: "Could not save Item") + /// Country label for a button, title, etc. + public static let country = Strings.tr("Localizable", "Country", fallback: "Country") + /// Text shown for the action create new file + public static let createNewFile = Strings.tr("Localizable", "Create new file", fallback: "Create new file") + /// Title shown in a page of the on boarding screens explaining that the user can add contacts to chat and colaborate + public static let createYourNetwork = Strings.tr("Localizable", "Create your Network", fallback: "Create your network") + /// Button title which triggers the action to create a MEGA account + public static let createAccount = Strings.tr("Localizable", "createAccount", fallback: "Create account") + /// Title button for the create folder alert. + public static let createFolderButton = Strings.tr("Localizable", "createFolderButton", fallback: "Create") + /// SDK error returned when the credit card has been rejected + public static let creditCardRejected = Strings.tr("Localizable", "Credit card rejected", fallback: "Credit card rejected") + /// Footer text that explain what amount of space you will free up if 'Clear Offline data', 'Clear cache' or 'Clear Rubbish Bin' is tapped + public static func currentlyUsing(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "currentlyUsing", p1, fallback: "Currently using %s") + } + /// Title of section to display information of the current version of a file. + public static let currentVersion = Strings.tr("Localizable", "currentVersion", fallback: "Current version") + /// Title of section to display information of all current versions of files. + public static let currentVersions = Strings.tr("Localizable", "currentVersions", fallback: "Current versions") + /// Action text to change to Custom Alphanumeric passcode type. + public static let customAlphanumericCode = Strings.tr("Localizable", "Custom Alphanumeric Code", fallback: "Custom alphanumeric code") + /// Title for custom settings + public static let customSettings = Strings.tr("Localizable", "Custom Settings", fallback: "Custom settings") + /// Used within the `Retention History` dropdown -- opens the dialog providing the ability to specify custom time range. + public static let custom = Strings.tr("Localizable", "Custom...", fallback: "Custom…") + /// Title of one of the Settings sections where you can see the MEGA's 'Data Protection Regulation' + public static let dataProtectionRegulationLabel = Strings.tr("Localizable", "dataProtectionRegulationLabel", fallback: "Data protection regulation") + /// Label for User Interface app icon "Day" in app settings + public static let day = Strings.tr("Localizable", "day", fallback: "Day") + /// + public static let days = Strings.tr("Localizable", "days", fallback: "days") + /// Button title to try to decrypt the link + public static let decrypt = Strings.tr("Localizable", "decrypt", fallback: "Decrypt") + /// Hint text to suggest that the user has to write the decryption key + public static let decryptionKey = Strings.tr("Localizable", "decryptionKey", fallback: "Decryption key") + /// Alert message 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 + public static let decryptionKeyAlertMessage = Strings.tr("Localizable", "decryptionKeyAlertMessage", fallback: "To access this folder or file, you will need its decryption key. If you do not have the key, please contact the creator of the link.") + /// Alert message shown when you tap on a encrypted album/collection link that can't be opened because it doesn't include the key to see its contents + public static let decryptionKeyAlertMessageForAlbum = Strings.tr("Localizable", "decryptionKeyAlertMessageForAlbum", fallback: "To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you.") + /// 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 + public static let decryptionKeyAlertTitle = Strings.tr("Localizable", "decryptionKeyAlertTitle", fallback: "Enter decryption key") + /// Alert title shown when you have written a decryption key not valid + public static let decryptionKeyNotValid = Strings.tr("Localizable", "decryptionKeyNotValid", fallback: "Invalid decryption key") + /// Label for any ‘Default’ button, link, text, title, etc. - (String as short as possible). + public static let `default` = Strings.tr("Localizable", "Default", fallback: "Default") + /// Inside of Settings - User Interface, there is a view on which you can change the default tab when launch the app. + public static let defaultTab = Strings.tr("Localizable", "Default Tab", fallback: "Default tab") + /// + public static let delete = Strings.tr("Localizable", "delete", fallback: "Delete") + /// The title of the section about deleting file versions in the settings. + public static let deleteAllOlderVersionsOfMyFiles = Strings.tr("Localizable", "Delete all older versions of my files", fallback: "Delete all older versions of my files") + /// Text of a button which deletes all historical versions of files in the users entire account. + public static let deletePreviousVersions = Strings.tr("Localizable", "Delete Previous Versions", fallback: "Delete previous versions") + /// A notification telling the user that the other user deleted them as a contact. E.g. user@email.com deleted you as a contact. + public static let deletedYouAsAContact = Strings.tr("Localizable", "Deleted you as a contact", fallback: "Deleted you as a contact") + /// Button which allows to delete message in chat conversation. + public static let deleteMessage = Strings.tr("Localizable", "deleteMessage", fallback: "Delete message") + /// Question to ensure user wants to delete file version. + public static let deleteVersion = Strings.tr("Localizable", "deleteVersion", fallback: "Do you want to delete this version?") + /// When somebody denied your contact request + public static let deniedYourContactRequest = Strings.tr("Localizable", "Denied your contact request", fallback: "Rejected your contact request") + /// Description shown when you almost had used your available transfer quota. + public static let depletedTransferQuotaMessage = Strings.tr("Localizable", "depletedTransferQuota_message", fallback: "Your queued download exceeds the current transfer quota available for your account and may therefore be interrupted.") + /// Title shown when you almost had used your available transfer quota. + public static let depletedTransferQuotaTitle = Strings.tr("Localizable", "depletedTransferQuota_title", fallback: "Insufficient transfer quota") + /// Text used for a title or header listing the details of something. + public static let details = Strings.tr("Localizable", "DETAILS", fallback: "Details") + /// Title to show when device local storage is almost full + public static let deviceStorageAlmostFull = Strings.tr("Localizable", "Device Storage Almost Full", fallback: "Device storage almost full") + /// Alert message shown when the DEBUG mode is disabled + public static let disableDebugModeMessage = Strings.tr("Localizable", "disableDebugMode_message", fallback: "The log (MEGAiOS.log) will be deleted from the Offline section.") + /// Alert title shown when the DEBUG mode is disabled + public static let disableDebugModeTitle = Strings.tr("Localizable", "disableDebugMode_title", fallback: "Disable debug mode") + /// button caption text that the user clicks when he disagrees + public static let disagree = Strings.tr("Localizable", "disagree", fallback: "Disagree") + /// Text used to notify the user that some action would discard a non ended action + public static let discardChanges = Strings.tr("Localizable", "Discard Changes", fallback: "Discard changes") + /// Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible). + public static let dismiss = Strings.tr("Localizable", "dismiss", fallback: "Dismiss") + /// File Manager -> Context menu item for taken down file or folder, for dispute takedown. + public static let disputeTakedown = Strings.tr("Localizable", "Dispute Takedown", fallback: "Dispute takedown") + /// Chat settings: This text appears with the Do Not Disturb switch + public static let doNotDisturb = Strings.tr("Localizable", "Do Not Disturb", fallback: "Do not disturb") + /// Confirmation dialog for the button that logs the user out of all sessions except the current one. + public static let doYouWantToCloseAllOtherSessionsThisWillLogYouOutOnAllOtherActiveSessionsExceptTheCurrentOne = Strings.tr("Localizable", "Do you want to close all other sessions? This will log you out on all other active sessions except the current one.", fallback: "Do you want to close all other sessions? This will log you out on all other active sessions except the current one.") + /// Message shown when a link is shared having a password + public static let doYouWantToShareThePasswordForThisLink = Strings.tr("Localizable", "Do you want to share the password for this link?", fallback: "Do you want to share the password for this link?") + /// Home Screen: Explorer view card title - Documents + public static let docs = Strings.tr("Localizable", "Docs", fallback: "Docs") + /// A tooltip message which is shown when device does not support document scanning + public static let documentScanningIsNotAvailable = Strings.tr("Localizable", "Document scanning is not available", fallback: "Document scanning is not available") + /// Title for document explorer view + public static let documents = Strings.tr("Localizable", "Documents", fallback: "Documents") + /// + public static let done = Strings.tr("Localizable", "done", fallback: "Done") + /// Text next to a toggle that if enabled won't appear again + public static let dontShowAgain = Strings.tr("Localizable", "dontShowAgain", fallback: "Do not show again") + /// Text next to a switch that allows disabling the HTTP protocol for transfers + public static let dontUseHttp = Strings.tr("Localizable", "dontUseHttp", fallback: "Don’t use HTTP") + /// + public static let download = Strings.tr("Localizable", "download", fallback: "Download") + /// Title of the dialog displayed the first time that a user want to download a image. Asks if wants to export image files to the photo album after download in future and informs that user can change this option afterwards - (String as short as possible). + public static let downloadOptions = Strings.tr("Localizable", "Download options", fallback: "Download options") + /// Button title which downloads a file/folder to your device + public static let downloadButton = Strings.tr("Localizable", "downloadButton", fallback: "Download") + /// Title show when a file is being downloaded + public static let downloading = Strings.tr("Localizable", "downloading", fallback: "Downloading") + /// Label for the status of a transfer when is being Downloading - (String as short as possible. + public static func downloading(_ p1: Any) -> String { + return Strings.tr("Localizable", "Downloading %@", String(describing: p1), fallback: "Downloading %@") + } + /// Title of one of the filters in the Transfers section. In this case "Downloads" transfers. + public static let downloads = Strings.tr("Localizable", "downloads", fallback: "Downloads") + /// Message shown when a download starts + public static let downloadStarted = Strings.tr("Localizable", "downloadStarted", fallback: "Download started") + /// Action suggestion text to the user in voice recording view + public static let dragLeftToCancelReleaseToSend = Strings.tr("Localizable", "Drag left to cancel, release to send", fallback: "Drag left to cancel, release to send") + /// Displayed after a call had ended, where %@ is the duration of the call (1h, 10seconds, etc) + public static func duration(_ p1: Any) -> String { + return Strings.tr("Localizable", "duration", String(describing: p1), fallback: "Duration: %@") + } + /// Caption of a button to edit the files that are selected + public static let edit = Strings.tr("Localizable", "edit", fallback: "Edit") + /// Contact details screen: Edit the alias(nickname) for a user + public static let editNickname = Strings.tr("Localizable", "Edit Nickname", fallback: "Edit nickname") + /// A log message in a chat to indicate that the message has been edited by the user. + public static let edited = Strings.tr("Localizable", "edited", fallback: "(edited)") + /// Text to notify user an email has been sent + public static let emailSent = Strings.tr("Localizable", "Email sent", fallback: "Email sent") + /// Error message when a use wants to validate the same email twice + public static let emailAddressChangeAlreadyRequested = Strings.tr("Localizable", "emailAddressChangeAlreadyRequested", fallback: "You have already requested a confirmation link for that email address.") + /// Error shown when the user tries to change his mail to one that is already used + public static let emailAlreadyInUse = Strings.tr("Localizable", "emailAlreadyInUse", fallback: "Error. This email address is already in use.") + /// Error text shown when the users tries to create an account with an email already in use + public static let emailAlreadyRegistered = Strings.tr("Localizable", "emailAlreadyRegistered", fallback: "This email address has already registered an account with MEGA") + /// Message shown when the user writes an invalid format in the email field + public static let emailInvalidFormat = Strings.tr("Localizable", "emailInvalidFormat", fallback: "Enter a valid email") + /// Text shown just after tap to change an email account to remenber the user what to do to complete the change email proccess + public static let emailIsChangingDescription = Strings.tr("Localizable", "emailIsChanging_description", fallback: "Please go to your inbox and click the link to confirm your new email address.") + /// Hint text to suggest that the user has to write his email + public static let emailPlaceholder = Strings.tr("Localizable", "emailPlaceholder", fallback: "Email") + /// Error message shown when you have not written the same email when confirming a new one + public static let emailsDoNotMatch = Strings.tr("Localizable", "emailsDoNotMatch", fallback: "Email addresses do not match") + /// Title shown when a folder doesn't have any files + public static let emptyFolder = Strings.tr("Localizable", "emptyFolder", fallback: "Empty folder") + /// Button text on the Rubbish Bin page. This button will empty all files and folders currently stored in the rubbish bin. + public static let emptyRubbishBin = Strings.tr("Localizable", "emptyRubbishBin", fallback: "Empty Rubbish bin") + /// Alert title shown when you tap 'Empty Rubbish Bin' + public static let emptyRubbishBinAlertTitle = Strings.tr("Localizable", "emptyRubbishBinAlertTitle", fallback: "All the items in the Rubbish bin will be deleted") + /// Text button shown when the chat is disabled and if tapped the chat will be enabled + public static let enable = Strings.tr("Localizable", "enable", fallback: "Enable") + /// Title label that explains that the user is going to be asked for the contacts permission + public static let enableAccessToYourAddressBook = Strings.tr("Localizable", "Enable Access to Your Address Book", fallback: "Grant access to your address book") + /// Title shown in a cell to allow the users enable the 'Encrypted Key Rotation' + public static let enableEncryptedKeyRotation = Strings.tr("Localizable", "Enable Encrypted Key Rotation", fallback: "Enable Encryption key rotation") + /// Title label that explains that the user is going to be asked for the microphone and camera permission + public static let enableMicrophoneAndCamera = Strings.tr("Localizable", "Enable Microphone and Camera", fallback: "Enable microphone and camera") + /// Title label that explains that the user is going to be asked for the notifications permission + public static let enableNotifications = Strings.tr("Localizable", "Enable Notifications", fallback: "Enable notifications") + /// Button title that enables the functionality 'Camera Uploads', which uploads all the photos in your device to MEGA + public static let enableCameraUploadsButton = Strings.tr("Localizable", "enableCameraUploadsButton", fallback: "Enable Camera uploads") + /// The label of the toggle switch to indicate that file versioning is enabled. + public static let enabled = Strings.tr("Localizable", "Enabled", fallback: "Enabled") + /// Alert message shown when the DEBUG mode is enabled + public static let enableDebugModeMessage = Strings.tr("Localizable", "enableDebugMode_message", fallback: "A log will be created in the Offline section (MEGAiOS.log). Logs can contain information related to your account.") + /// Alert title shown when the DEBUG mode is enabled + public static let enableDebugModeTitle = Strings.tr("Localizable", "enableDebugMode_title", fallback: "Enable debug mode") + /// 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. + public static let enableRichUrlPreviews = Strings.tr("Localizable", "enableRichUrlPreviews", fallback: "Enable rich URL previews") + /// The text of a button. This button will encrypt a link with a password. + public static let encrypt = Strings.tr("Localizable", "encrypt", fallback: "Encrypt") + /// The text of a button. This button will encrypt a link with a password. + public static let encrypted = Strings.tr("Localizable", "encrypted", fallback: "Encrypted") + /// Title shown in a page of the on boarding screens explaining that the chat is encrypted + public static let encryptedChat = Strings.tr("Localizable", "Encrypted chat", fallback: "Encrypted chat") + /// Label in a cell where you can enable the 'Encrypted Key Rotation' + public static let encryptedKeyRotation = Strings.tr("Localizable", "Encrypted Key Rotation", fallback: "Encryption key rotation") + /// Text used as a section title or similar + public static let enterEmail = Strings.tr("Localizable", "Enter Email", fallback: "Enter email") + /// Title of the dialog shown when the user it is creating a chat link and the chat has not title + public static let enterGroupName = Strings.tr("Localizable", "Enter group name", fallback: "Enter group name") + /// This placeholder text is used on the Password Decrypt dialog as an instruction for the user. + public static let enterThePassword = Strings.tr("Localizable", "Enter the password", fallback: "Enter the password") + /// Text shown in the last alert dialog to confirm the cancellation of an account + public static let enterYourPasswordToConfirmThatYouWanToClose = Strings.tr("Localizable", "enterYourPasswordToConfirmThatYouWanToClose", fallback: "This is the last step to delete your account. You will permanently lose all the data stored in the cloud. Please enter your password below.") + /// Title of one of the passcode options. If enabled will do what footer explains. + public static let eraseAllLocalDataLabel = Strings.tr("Localizable", "eraseAllLocalDataLabel", fallback: "Erase local data") + /// + public static let error = Strings.tr("Localizable", "error", fallback: "Error") + /// Label to show that an error related with expiration occurs during a SDK operation. + public static let expired = Strings.tr("Localizable", "Expired", fallback: "Expired") + /// Text that shows the expiry date of the account PRO level + public static func expiresOn(_ p1: Any) -> String { + return Strings.tr("Localizable", "expiresOn", String(describing: p1), fallback: "expires on %@") + } + /// Text for options in Get Link View to activate expiry date + public static let expiryDate = Strings.tr("Localizable", "Expiry Date", fallback: "Expiry date") + /// Hint text for option separate the key from the link in Get Link View + public static let exportTheLinkAndDecryptionKeySeparately = Strings.tr("Localizable", "Export the link and decryption key separately.", fallback: "Export the link and decryption key separately.") + /// Advising the user to keep the user's master key safe. + public static let exportMasterKeyFooter = Strings.tr("Localizable", "exportMasterKeyFooter", fallback: "Exporting the Recovery key and keeping it in a secure location enables you to set a new password without data loss.") + /// A dialog title to export the Recovery Key for the current user. + public static let exportRecoveryKey = Strings.tr("Localizable", "exportRecoveryKey", fallback: "Export Recovery key") + /// Label to show that a SDK operation has failed permanently. + public static let failedPermanently = Strings.tr("Localizable", "Failed permanently", fallback: "Failed permanently") + /// A message shown to users when phone number removal fails. + public static let failedToRemoveYourPhoneNumberPleaseTryAgainLater = Strings.tr("Localizable", "Failed to remove your phone number, please try again later.", fallback: "Failed to remove your phone number, please try again later.") + /// Accessibility message when a message sending fails in chat + public static let failedToSendTheMessage = Strings.tr("Localizable", "failed to send the message", fallback: "failed to send the message") + /// Footer text that explain what will happen if reach the max number of failed attempts + public static let failedAttempstSectionTitle = Strings.tr("Localizable", "failedAttempstSectionTitle", fallback: "You will be logged out and your offline files will be deleted after 10 failed attempts") + /// Alert message shown when a purchase has stopped because some error between the process + public static let failedPurchaseMessage = Strings.tr("Localizable", "failedPurchase_message", fallback: "Either you cancelled the request or Apple reported a transaction error. Please try again later, or contact ios@mega.nz.") + /// Alert title shown when a purchase has stopped because some error between the process + public static let failedPurchaseTitle = Strings.tr("Localizable", "failedPurchase_title", fallback: "Purchase stopped") + /// Alert message shown when the restoring process has stopped for some reason + public static let failedRestoreMessage = Strings.tr("Localizable", "failedRestore_message", fallback: "Either the request was cancelled or the prior purchase could not be restored. Please try again later, or contact ios@mega.nz.") + /// Alert title shown when the restoring process has stopped for some reason + public static let failedRestoreTitle = Strings.tr("Localizable", "failedRestore_title", fallback: "Restore stopped") + /// Context menu item. Allows user to add file/folder to favourites + public static let favourite = Strings.tr("Localizable", "Favourite", fallback: "Favourite") + /// Text for title for favourite nodes + public static let favourites = Strings.tr("Localizable", "Favourites", fallback: "Favourites") + /// Singular of file. 1 file + public static let file = Strings.tr("Localizable", "file", fallback: "file") + /// Error message shown when opening a file link which doesn’t exist + public static let fileLinkUnavailable = Strings.tr("Localizable", "File link unavailable", fallback: "File link unavailable") + /// A section header which contains the file management settings. These settings allow users to remove duplicate files etc. + public static let fileManagement = Strings.tr("Localizable", "File Management", fallback: "File management") + /// file type title, used in changing the export format of scaned doc + public static let fileType = Strings.tr("Localizable", "File Type", fallback: "File Type") + /// Title of the option to enable or disable file versioning on Settings section + public static let fileVersioning = Strings.tr("Localizable", "File versioning", fallback: "File versioning") + /// Settings preference title to show file versions info of the account + public static let fileVersions = Strings.tr("Localizable", "File Versions", fallback: "File versions") + /// Hint text shown on the new text file alert. + public static let fileName = Strings.tr("Localizable", "file_name", fallback: "File name") + /// 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}" + public static let fileExistAlertControllerMessage = Strings.tr("Localizable", "fileExistAlertController_Message", fallback: "[A] already uploaded with the name [B]") + /// Success message shown when you have moved 1 file and 1 folder to the rubbish bin + public static let fileFolderMovedToRubbishBinMessage = Strings.tr("Localizable", "fileFolderMovedToRubbishBinMessage", fallback: "1 file and 1 folder moved to the Rubbish bin") + /// Success message shown when 1 file and 1 folder have been removed from MEGA + public static let fileFolderRemovedToRubbishBinMessage = Strings.tr("Localizable", "fileFolderRemovedToRubbishBinMessage", fallback: "1 file and 1 folder removed from MEGA") + /// Success message shown when you have moved 1 file and {1+} folders to the rubbish bin + public static func fileFoldersMovedToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "fileFoldersMovedToRubbishBinMessage", p1, fallback: "1 file and %d folders moved to the Rubbish bin") + } + /// Success message shown when 1 file and {1+} folders have been removed from MEGA + public static func fileFoldersRemovedToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "fileFoldersRemovedToRubbishBinMessage", p1, fallback: "1 file and %d folders removed from MEGA") + } + /// Message shown when a file has been imported + public static let fileImported = Strings.tr("Localizable", "fileImported", fallback: "File imported") + /// Title for the file link view + public static let fileLink = Strings.tr("Localizable", "fileLink", fallback: "File link") + /// + public static let fileLinkUnavailableText1 = Strings.tr("Localizable", "fileLinkUnavailableText1", fallback: "This could be due to the following reasons:") + /// ToS refers to Terms of Service and AUP refers to Acceptable Use Policy. + public static let fileLinkUnavailableText2 = Strings.tr("Localizable", "fileLinkUnavailableText2", fallback: "The file has been removed as it violated our Terms of Service") + /// + public static let fileLinkUnavailableText3 = Strings.tr("Localizable", "fileLinkUnavailableText3", fallback: "Invalid URL - the link you are trying to access does not exist") + /// + public static let fileLinkUnavailableText4 = Strings.tr("Localizable", "fileLinkUnavailableText4", fallback: "The file has been deleted by the user.") + /// Success message shown when you have moved 1 file to the rubbish bin + public static let fileMovedToRubbishBinMessage = Strings.tr("Localizable", "fileMovedToRubbishBinMessage", fallback: "1 file moved to the Rubbish bin") + /// Alert title shown when users try to stream an unsupported audio/video file + public static let fileNotSupported = Strings.tr("Localizable", "fileNotSupported", fallback: "File not supported") + /// Success message shown when 1 file has been removed from MEGA + public static let fileRemovedToRubbishBinMessage = Strings.tr("Localizable", "fileRemovedToRubbishBinMessage", fallback: "1 file removed from MEGA") + /// Subtitle shown on folders that gives you information about its content. This case "{1+} files" + public static func files(_ p1: Int) -> String { + return Strings.tr("Localizable", "files", p1, fallback: "%d files") + } + /// Message shown when you try to upload some photos or/and videos that are already uploaded in the current folder + public static func filesAlreadyExistMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "filesAlreadyExistMessage", p1, fallback: "%d files selected were already uploaded into this folder.") + } + /// Toast text upon sending a single file to chat + public static let fileSentToChat = Strings.tr("Localizable", "fileSentToChat", fallback: "File sent to chat") + /// Success message when the attachment has been sent to a many chats + public static func fileSentToXChats(_ p1: Int) -> String { + return Strings.tr("Localizable", "fileSentToXChats", p1, fallback: "File sent to %1$d chats") + } + /// Success message shown when you have moved {1+} files and 1 folder to the rubbish bin + public static func filesFolderMovedToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "filesFolderMovedToRubbishBinMessage", p1, fallback: "%d files and 1 folder moved to the Rubbish bin") + } + /// Success message shown when {1+} files and 1 folder have been removed from MEGA + public static func filesFolderRemovedToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "filesFolderRemovedToRubbishBinMessage", p1, fallback: "%d files and 1 folder removed from MEGA") + } + /// Success message shown when you have moved [A] = {1+} files and [B] = {1+} folders to the rubbish bin + public static let filesFoldersMovedToRubbishBinMessage = Strings.tr("Localizable", "filesFoldersMovedToRubbishBinMessage", fallback: "[A] files and [B] folders moved to the Rubbish bin") + /// Success message shown when [A] = {1+} files and [B] = {1+} folders have been removed from MEGA + public static let filesFoldersRemovedToRubbishBinMessage = Strings.tr("Localizable", "filesFoldersRemovedToRubbishBinMessage", fallback: "[A] files and [B] folders removed from MEGA") + /// Message shown when some files have been imported + public static let filesImported = Strings.tr("Localizable", "filesImported", fallback: "Files imported") + /// Success message shown when you have moved {1+} files to the rubbish bin + public static func filesMovedToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "filesMovedToRubbishBinMessage", p1, fallback: "%d files moved to the Rubbish bin") + } + /// Success message shown when {1+} files have been removed from MEGA + public static func filesRemovedToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "filesRemovedToRubbishBinMessage", p1, fallback: "%d files removed from MEGA") + } + /// Toast text upon sending multiple files to chat + public static let filesSentToChat = Strings.tr("Localizable", "filesSentToChat", fallback: "Files sent to chat") + /// Error message shown when you try to download something bigger than the space available in your device + public static let fileTooBigMessage = Strings.tr("Localizable", "fileTooBigMessage", fallback: "The file you are trying to download is bigger than the available memory.") + /// Error message shown when you try to open something bigger than the free space in your device + public static let fileTooBigMessageOpen = Strings.tr("Localizable", "fileTooBigMessage_open", fallback: "The file you are trying to open is bigger than the available memory.") + /// Filter headline in the Filter UI on Camera Upload Timeline Screen + public static let filter = Strings.tr("Localizable", "filter", fallback: "Filter") + /// Hint text for the first name (Placeholder) + public static let firstName = Strings.tr("Localizable", "firstName", fallback: "First name") + /// + public static let folder = Strings.tr("Localizable", "folder", fallback: "Folder") + /// Error message shown when opening a folder link which doesn’t exist + public static let folderLinkUnavailable = Strings.tr("Localizable", "Folder link unavailable", fallback: "Folder link unavailable") + /// Error message shown when a folder can't be created. + public static func folderCreationError(_ p1: Any) -> String { + return Strings.tr("Localizable", "folderCreationError", String(describing: p1), fallback: "The folder “%@” can’t be created") + } + /// Error message shown when a user tries to download a folder with the name Inbox on the main directory of the offline section + public static let folderInboxError = Strings.tr("Localizable", "folderInboxError", fallback: "Inbox is reserved for use by Apple.") + /// Title for the folder link view + public static let folderLink = Strings.tr("Localizable", "folderLink", fallback: "Folder link") + /// + public static let folderLinkUnavailableText1 = Strings.tr("Localizable", "folderLinkUnavailableText1", fallback: "This could be due to the following reasons:") + /// + public static let folderLinkUnavailableText2 = Strings.tr("Localizable", "folderLinkUnavailableText2", fallback: "The folder link has been removed as it violated our Terms of Service.") + /// + public static let folderLinkUnavailableText3 = Strings.tr("Localizable", "folderLinkUnavailableText3", fallback: "Invalid URL - the link you are trying to access does not exist") + /// + public static let folderLinkUnavailableText4 = Strings.tr("Localizable", "folderLinkUnavailableText4", fallback: "The folder link has been disabled by the user.") + /// Success message shown when you have moved 1 folder to the rubbish bin + public static let folderMovedToRubbishBinMessage = Strings.tr("Localizable", "folderMovedToRubbishBinMessage", fallback: "1 folder moved to the Rubbish bin") + /// Success message shown when 1 folder has been removed from MEGA + public static let folderRemovedToRubbishBinMessage = Strings.tr("Localizable", "folderRemovedToRubbishBinMessage", fallback: "1 folder removed from MEGA") + /// Success message shown when you have moved {1+} folders to the rubbish bin + public static func foldersMovedToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "foldersMovedToRubbishBinMessage", p1, fallback: "%d folders moved to the Rubbish bin") + } + /// Success message shown when {1+} folders have been removed from MEGA + public static func foldersRemovedToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "foldersRemovedToRubbishBinMessage", p1, fallback: "%d folders removed from MEGA") + } + /// Subtitle shown on the Contacts section under the name of the contact you have shared {Number of folders} with + public static func foldersShared(_ p1: Int) -> String { + return Strings.tr("Localizable", "foldersShared", p1, fallback: "%d folders") + } + /// Message shown when you try to downlonad a folder bigger than the available memory. + public static let folderTooBigMessage = Strings.tr("Localizable", "folderTooBigMessage", fallback: "The folder you are trying to download is bigger than the available memory.") + /// An option to reset the password. + public static let forgotPassword = Strings.tr("Localizable", "forgotPassword", fallback: "Forgot password?") + /// Item of a menu to forward a message chat to another chatroom + public static let forward = Strings.tr("Localizable", "forward", fallback: "Forward") + /// Label to indicate that the current user has a Free account. + public static let free = Strings.tr("Localizable", "Free", fallback: "Free") + /// + public static let fromCloudDrive = Strings.tr("Localizable", "fromCloudDrive", fallback: "From Cloud drive") + /// Permissions given to the user you share your folder with + public static let fullAccess = Strings.tr("Localizable", "fullAccess", fallback: "Full access") + /// Description shown in a page of the onboarding screens explaining the chat feature + public static let fullyEncryptedChatWithVoiceAndVideoCallsGroupMessagingAndFileSharingIntegrationWithYourCloudDrive = Strings.tr("Localizable", "Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud Drive.", fallback: "Fully encrypted chat with voice and video calls, group messaging and file sharing integration with your Cloud drive.") + /// Message shown when a link to a file or folder is being generated + public static let generatingLink = Strings.tr("Localizable", "generatingLink", fallback: "Generating link…") + /// Message shown when some links to files and/or folders are being generated + public static let generatingLinks = Strings.tr("Localizable", "generatingLinks", fallback: "Generating links…") + /// Label in a cell where you can get the chat link + public static let getChatLink = Strings.tr("Localizable", "Get Chat Link", fallback: "Get chat link") + /// + public static let good = Strings.tr("Localizable", "good", fallback: "Good") + /// A user can mark a folder or file with its own colour, in this case “Green”. + public static let green = Strings.tr("Localizable", "Green", fallback: "Green") + /// A user can mark a folder or file with its own colour, in this case “Grey”. + public static let grey = Strings.tr("Localizable", "Grey", fallback: "Grey") + /// When an active goup call is ended + public static let groupCallEnded = Strings.tr("Localizable", "Group call ended", fallback: "Group call ended") + /// Label title for a group chat + public static let groupChat = Strings.tr("Localizable", "groupChat", fallback: "Group chat") + /// Label for any ‘Groups’ button, link, text, title, etc. On iOS is used to go to the chats 'Groups' section from Contacts + public static let groups = Strings.tr("Localizable", "Groups", fallback: "Groups") + /// Menu item + public static let help = Strings.tr("Localizable", "help", fallback: "Help") + /// Title of the section to access MEGA's help centre + public static let helpCentreLabel = Strings.tr("Localizable", "helpCentreLabel", fallback: "Help Centre") + /// Setting title for the feature that deletes messages automatically from a chat after a period of time + public static let historyClearing = Strings.tr("Localizable", "History Clearing", fallback: "History clearing") + /// Accessibility label for Home section in tabbar item + public static let home = Strings.tr("Localizable", "Home", fallback: "Home") + /// + public static let howItWorks = Strings.tr("Localizable", "howItWorks", fallback: "How it works") + /// + public static let howItWorksMain = Strings.tr("Localizable", "howItWorksMain", fallback: "Your friend needs to register for a free account on MEGA and [S]install at least one MEGA client application[/S] (either the MEGA Desktop App or one of our MEGA Mobile Apps)") + /// + public static let howItWorksSecondary = Strings.tr("Localizable", "howItWorksSecondary", fallback: "You can notify your friend through any method. You will earn the quota if you have entered your friend’s email here prior to them registering with that address.") + /// A message which is shown once someone has invited a friend as part of the achievements program. + public static let howItWorksTertiary = Strings.tr("Localizable", "howItWorksTertiary", fallback: "You will not receive credit for inviting someone who has used MEGA previously and you will not be notified about such a rejection.") + /// SDK error returned upon a HTTP error + public static let httpError = Strings.tr("Localizable", "HTTP error", fallback: "HTTP error") + /// Used in camera upload settings: This text will appear below the 'Include location tags' settings explaining the details of this settings + public static let ifEnabledYouWillUploadInformationAboutWhereYourPicturesAndVideosWereTakenSoBeCarefulWhenSharingThem = Strings.tr("Localizable", "If enabled, you will upload information about where your pictures and videos were taken, so be careful when sharing them.", fallback: "If enabled, location information will be included with your pictures. Please be careful when sharing them.") + /// This dialog message is used on the Password Decrypt dialog as an instruction for the user. + public static let ifYouDoNotHaveThePasswordContactTheCreatorOfTheLink = Strings.tr("Localizable", "If you do not have the password, contact the creator of the link.", fallback: "If you do not have the password, contact the creator of the link.") + /// + public static let ifYouLoseThisRecoveryKeyAndForgetYourPasswordBAllYourFilesFoldersAndMessagesWillBeInaccessibleEvenByMEGAB = Strings.tr("Localizable", "If you lose this Recovery key and forget your password, [B]all your files, folders and messages will be inaccessible, even by MEGA[/B].", fallback: "If you lose this Recovery key and forget your password, [B]all your files, folders and messages will be inaccessible, even by MEGA[/B].") + /// Account closure, warning message to remind user to contact MEGA support after he confirms that he wants to cancel account. + public static let ifYouCantAccessYourEmailAccount = Strings.tr("Localizable", "ifYouCantAccessYourEmailAccount", fallback: "If you can’t access your email, please contact support@mega.nz") + /// Button title to allow the user ignore something + public static let ignore = Strings.tr("Localizable", "ignore", fallback: "Ignore") + /// Label used near to the option selected to encode the images uploaded to a chat (Automatic, High, Optimised) + public static let imageQuality = Strings.tr("Localizable", "Image Quality", fallback: "Image quality") + /// Footer text shown under the settings for download options 'Save Images/Videos in Library' + public static let imagesAndOrVideosDownloadedWillBeStoredInTheDeviceSMediaLibraryInsteadOfTheOfflineSection = Strings.tr("Localizable", "Images and/or videos downloaded will be stored in the device’s media library instead of the Offline section.", fallback: "Images or videos downloaded will be stored in the device’s media library instead of the Offline section.") + /// Label indicating that the enter passcode (pin) view will be displayed immediately if the application goes back to foreground after being in background. + public static let immediately = Strings.tr("Localizable", "Immediately", fallback: "Immediately") + /// Button title that triggers the importing link action + public static let importToCloudDrive = Strings.tr("Localizable", "Import to Cloud Drive", fallback: "Import") + /// Title of one of the filters in the Transfers section. In this case In Progress transfers + public static let inProgress = Strings.tr("Localizable", "In Progress", fallback: "In progress") + /// Subtitle of chat screen when the chat is inactive + public static let inactiveChat = Strings.tr("Localizable", "Inactive chat", fallback: "Inactive chat") + /// Used in camera upload settings: This text will appear with a switch to turn on/off location tags while uploading a file + public static let includeLocationTags = Strings.tr("Localizable", "Include Location Tags", fallback: "Include location tags") + /// Title of the "Incoming" Shared Items. + public static let incoming = Strings.tr("Localizable", "incoming", fallback: "Incoming") + /// notification subtitle of incoming calls + public static let incomingCall = Strings.tr("Localizable", "Incoming call", fallback: "Incoming call") + /// Title of the Incoming Shares section. Here you can see the folders that your contacts have shared with you + public static let incomingShares = Strings.tr("Localizable", "incomingShares", fallback: "Incoming shares") + /// Label to show that an error related with an Incomplete SDK operation. + public static let incomplete = Strings.tr("Localizable", "Incomplete", fallback: "Incomplete") + /// Alert message shown when a restore hasn't been completed correctly + public static let incompleteRestoreMessage = Strings.tr("Localizable", "incompleteRestore_message", fallback: "The previous purchase could not be found. Please select the previously purchased product to restore. You will NOT be charged again.") + /// Alert title shown when a restore hasn't been completed correctly + public static let incompleteRestoreTitle = Strings.tr("Localizable", "incompleteRestore_title", fallback: "Restore issue") + /// A button label. The button allows the user to get more info of the current context. + public static let info = Strings.tr("Localizable", "info", fallback: "Info") + /// + public static let insertYourFriendsEmails = Strings.tr("Localizable", "insertYourFriendsEmails", fallback: "Insert your friends’ emails:") + /// Label to show that an Internal error occurs during a SDK operation. + public static let internalError = Strings.tr("Localizable", "Internal error", fallback: "Internal error") + /// Label to show that an error related with an invalid or missing application key occurs during a SDK operation. + public static let invalidApplicationKey = Strings.tr("Localizable", "Invalid application key", fallback: "Invalid application key") + /// Label to show that an error of Invalid argument occurs during a SDK operation. + public static let invalidArgument = Strings.tr("Localizable", "Invalid argument", fallback: "Invalid argument") + /// Label to show that an error related with the decryption process of a node occurs during a SDK operation. + public static let invalidKeyDecryptionError = Strings.tr("Localizable", "Invalid key/Decryption error", fallback: "Decryption error") + /// Error text shown when the user scans a QR that is not valid. String as short as possible. + public static let invalidCode = Strings.tr("Localizable", "invalidCode", fallback: "Invalid code") + /// Message shown when the user writes a wrong email or password on login + public static let invalidMailOrPassword = Strings.tr("Localizable", "invalidMailOrPassword", fallback: "Invalid email or password. Please try again.") + /// An alert title where the user provided the incorrect Recovery Key. + public static let invalidRecoveryKey = Strings.tr("Localizable", "invalidRecoveryKey", fallback: "Invalid Recovery key") + /// A button on a dialog which invites a contact to join MEGA. + public static let invite = Strings.tr("Localizable", "invite", fallback: "Invite") + /// Text showing the user one contact would be invited + public static let invite1Contact = Strings.tr("Localizable", "Invite 1 contact", fallback: "Invite 1 contact") + /// Text showing the user how many contacts would be invited + public static let inviteXContacts = Strings.tr("Localizable", "Invite [X] contacts", fallback: "Invite [X] contacts") + /// Text emncouraging the user to add contacts in MEGA + public static let inviteContactNow = Strings.tr("Localizable", "Invite contact now", fallback: "Invite contacts now") + /// Text encouraging the user to invite contacts to MEGA + public static let inviteContactsAndStartChattingSecurelyWithMEGASEncryptedChat = Strings.tr("Localizable", "Invite contacts and start chatting securely with MEGA’s encrypted chat.", fallback: "Invite contacts and start chatting securely with MEGA’s encrypted chat.") + /// Text shown when the user tries to make a call and the receiver is not a contact + public static let inviteContact = Strings.tr("Localizable", "inviteContact", fallback: "Invite to MEGA") + /// Text shown when the user sends a contact invitation + public static let inviteSent = Strings.tr("Localizable", "inviteSent", fallback: "Invite sent") + /// A typing indicator in the chat. Please leave the %@ which will be automatically replaced with the user's name at runtime. + public static func isTyping(_ p1: Any) -> String { + return Strings.tr("Localizable", "isTyping", String(describing: p1), fallback: "%@ is typing…") + } + /// Message shown when there is an ongoing call and the user tries to play an audio or video + public static let itIsNotPossibleToPlayContentWhileThereIsACallInProgress = Strings.tr("Localizable", "It is not possible to play content while there is a call in progress", fallback: "It is not possible to play media files while there is a call in progress.") + /// Message shown when there is an ongoing call and the user tries to record a voice message + public static let itIsNotPossibleToRecordVoiceMessagesWhileThereIsACallInProgress = Strings.tr("Localizable", "It is not possible to record voice messages while there is a call in progress", fallback: "It is not possible to record voice messages while there is a call in progress.") + /// Locked accounts description text by an external data breach. This text is 1 of 2 paragraph of a description. + public static let itIsPossibleThatYouAreUsingTheSamePasswordForYourMEGAAccountAsForOtherServicesAndThatAtLeastOneOfTheseOtherServicesHasSufferedADataBreach = Strings.tr("Localizable", "It is possible that you are using the same password for your MEGA account as for other services, and that at least one of these other services has suffered a data breach.", fallback: "It is possible that you are using the same password for your MEGA account as for other services, and that at least one of these other services has suffered a data breach.") + /// Title shown on the Camera Uploads section when the edit mode is enabled and you have selected more than one photo + public static func itemsSelected(_ p1: Int) -> String { + return Strings.tr("Localizable", "itemsSelected", p1, fallback: "%lu items selected") + } + /// Button text in public chat previews that allows the user to join the chat + public static let join = Strings.tr("Localizable", "Join", fallback: "Join") + /// Section title that links you to the webpage that let you join and test the beta versions + public static let joinBeta = Strings.tr("Localizable", "Join Beta", fallback: "Join beta") + /// A log message in a chat conversation to tell the reader that a participant [A] was added to the chat by a moderator [B]. Please keep the [A] and [B] placeholders, they will be replaced by the participant and the moderator names at runtime. For example: Alice joined the group chat by invitation from Frank. + public static let joinedTheGroupChatByInvitationFrom = Strings.tr("Localizable", "joinedTheGroupChatByInvitationFrom", fallback: "[A] joined the group chat by invitation from [B].") + /// Label shown while joining a public chat + public static let joining = Strings.tr("Localizable", "Joining...", fallback: "Joining…") + /// Label in a button that allows to jump to the latest item + public static let jumpToLatest = Strings.tr("Localizable", "jumpToLatest", fallback: "Jump to latest") + /// Text presenting a key (for a LINK or similar) as header usually + public static let key = Strings.tr("Localizable", "KEY", fallback: "Key") + /// Message shown when the key has been copied to the Clipboard + public static let keyCopiedToClipboard = Strings.tr("Localizable", "Key Copied to Clipboard", fallback: "Key copied to clipboard") + /// Footer to explain why key rotation is disabled for public chats with many participants + public static let keyRotationIsDisabledForConversationsWithMoreThan100Participants = Strings.tr("Localizable", "Key rotation is disabled for conversations with more than 100 participants.", fallback: "Encryption key rotation is disabled for conversations with more than 100 participants.") + /// Footer text to explain what means 'Encrypted Key Rotation' + public static let keyRotationIsSlightlyMoreSecureButDoesNotAllowYouToCreateAChatLinkAndNewParticipantsWillNotSeePastMessages = Strings.tr("Localizable", "Key rotation is slightly more secure, but does not allow you to create a chat link and new participants will not see past messages.", fallback: "Encryption key rotation is slightly more secure, but does not allow you to create a chat link and new participants will not see past messages.") + /// Sort by option (3/6). This one order the files by its size, in this case from bigger to smaller size + public static let largest = Strings.tr("Localizable", "largest", fallback: "Largest") + /// Shown when viewing a 1on1 chat (at least for now), if the user is offline. + public static func lastSeenS(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "Last seen %s", p1, fallback: "Last seen: %s") + } + /// Text to inform the user the 'Last seen' time of a contact is a long time ago (>= 65535 minutes) + public static let lastSeenALongTimeAgo = Strings.tr("Localizable", "Last seen a long time ago", fallback: "Last seen a long time ago") + /// Hint text for the last name (Placeholder) + public static let lastName = Strings.tr("Localizable", "lastName", fallback: "Last name") + /// Button title to allow the user postpone an action + public static let later = Strings.tr("Localizable", "later", fallback: "Later") + /// Section title inside of Settings - User Interface, where you can change the app's launch. + public static let launch = Strings.tr("Localizable", "Launch", fallback: "Launch") + /// Text description leading the user to perform an action, ie the shortcuts widget + public static let launchTheMEGAAppToPerformAnAction = Strings.tr("Localizable", "Launch the MEGA app to perform an action", fallback: "Launch the MEGA App to perform an action") + /// Section title inside of Settings - Appearance, where you can change the app's layout. + public static let layout = Strings.tr("Localizable", "Layout", fallback: "Layout") + /// Label for any ‘Learn more’ button, link, text, title, etc. - (String as short as possible). + public static let learnMore = Strings.tr("Localizable", "Learn more", fallback: "Learn more") + /// A button label. The button allows the user to leave the group conversation. + public static let leave = Strings.tr("Localizable", "leave", fallback: "Leave") + /// Button title of the action that allows to leave a shared folder + public static let leaveFolder = Strings.tr("Localizable", "leaveFolder", fallback: "Leave") + /// Button title that allows the user to leave a group chat. + public static let leaveGroup = Strings.tr("Localizable", "leaveGroup", fallback: "Leave group") + /// Alert message shown when the user tap on the leave share action for one inshare + public static let leaveShareAlertMessage = Strings.tr("Localizable", "leaveShareAlertMessage", fallback: "Are you sure you want to leave this share?") + /// Alert message shown when the user tap on the leave share action selecting multiple inshares + public static let leaveSharesAlertMessage = Strings.tr("Localizable", "leaveSharesAlertMessage", fallback: "Are you sure you want to leave these shares?") + /// Label shown while leaving a public chat + public static let leaving = Strings.tr("Localizable", "Leaving...", fallback: "Leaving…") + /// 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. + public static let leftTheGroupChat = Strings.tr("Localizable", "leftTheGroupChat", fallback: "[A] left the group chat") + /// Label that encourage the user to line the QR to scan with the camera + public static let lineCodeWithCamera = Strings.tr("Localizable", "lineCodeWithCamera", fallback: "Line up the QR code to scan it with your device’s camera") + /// Text used as title or header for reference an url, for instance, a node link. + public static let link = Strings.tr("Localizable", "LINK", fallback: "Link") + /// Text referencing the date of creation of a link + public static let linkCreation = Strings.tr("Localizable", "Link Creation", fallback: "Link creation") + /// Text indicating the date until a link will be valid + public static func linkExpires(_ p1: Any) -> String { + return Strings.tr("Localizable", "Link expires %@", String(describing: p1), fallback: "Link expires %@") + } + /// Title for password protect link alert + public static let linkPassword = Strings.tr("Localizable", "Link Password", fallback: "Link password") + /// Message shown when the link has been copied to the pasteboard + public static let linkCopied = Strings.tr("Localizable", "linkCopied", fallback: "Copied link") + /// Message shown when the user clicks on an link that is not valid + public static let linkNotValid = Strings.tr("Localizable", "linkNotValid", fallback: "Invalid link") + /// Title of the "Links" Shared Items. + public static let links = Strings.tr("Localizable", "Links", fallback: "Links") + /// Message shown when some links have been copied to the pasteboard + public static let linksCopied = Strings.tr("Localizable", "linksCopied", fallback: "Copied links") + /// Error title shown when you open a file or folder link and it's no longer available + public static let linkUnavailable = Strings.tr("Localizable", "linkUnavailable", fallback: "Unavailable link") + /// This is button text on the Get Link dialog. This lets the user get a public file/folder link with the decryption key e.g. https://mega.nz/#!Qo12lSpT!3uv6GhJhAWWH46fcMN2KGRtxc_QSLthcwvAdaA_TjCE. + public static let linkWithKey = Strings.tr("Localizable", "linkWithKey", fallback: "Link with key") + /// This is button text on the Get Link dialog. This lets the user get a public file/folder link without the decryption key e.g. https://mega.nz/#!Qo12lSpT. + public static let linkWithoutKey = Strings.tr("Localizable", "linkWithoutKey", fallback: "Link without key") + /// Text shown for switching from thumbnail view to list view. + public static let listView = Strings.tr("Localizable", "List View", fallback: "List view") + /// state previous to import a file + public static let loading = Strings.tr("Localizable", "loading", fallback: "Loading…") + /// Title for "Local" used space. + public static let localLabel = Strings.tr("Localizable", "localLabel", fallback: "Local") + /// Title of a helping view about locked accounts + public static let lockedAccounts = Strings.tr("Localizable", "Locked Accounts", fallback: "Locked accounts") + /// Alert title shown when your account has been closed through another client or the web by killing all your sessions + public static let loggedOutAlertTitle = Strings.tr("Localizable", "loggedOut_alertTitle", fallback: "Logged out") + /// Alert message shown when your account has been closed through another client or the web by killing all your sessions + public static let loggedOutFromAnotherLocation = Strings.tr("Localizable", "loggedOutFromAnotherLocation", fallback: "You have been logged out of this device from another location") + /// String shown when you are logging out of your account. + public static let loggingOut = Strings.tr("Localizable", "loggingOut", fallback: "Logging out") + /// Button title which triggers the action to login in your MEGA account + public static let login = Strings.tr("Localizable", "login", fallback: "Log in") + /// Title of the button which logs out from your account. + public static let logoutLabel = Strings.tr("Localizable", "logoutLabel", fallback: "Log out") + /// A button to help them restore their account if they have lost their 2FA device. + public static let lostYourAuthenticatorDevice = Strings.tr("Localizable", "lostYourAuthenticatorDevice", fallback: "Lost your authenticator device?") + /// Footer text to explain the meaning of the functionaly 'Status Persistence' of your chat status. + public static let maintainMyChosenStatusAppearance = Strings.tr("Localizable", "maintainMyChosenStatusAppearance", fallback: "Maintain my chosen status appearance even when I have no connected devices.") + /// Text indicating to the user some action should be addressed. E.g. Navigate to Settings/File Management to clear cache. + public static let manage = Strings.tr("Localizable", "Manage", fallback: "Manage") + /// account management button title in business account’s landing page + public static let manageAccount = Strings.tr("Localizable", "Manage Account", fallback: "Manage account") + /// Text related with the section where you can manage the chat history. There you can for example, clear the history or configure the retention setting. + public static let manageChatHistory = Strings.tr("Localizable", "Manage Chat History", fallback: "Manage chat history") + /// Text indicating to the user the action that will be executed on tap. + public static let manageShare = Strings.tr("Localizable", "Manage Share", fallback: "Manage share") + /// Title of the alert that allows change between different maps: Standard, Satellite or Hybrid. + public static let mapSettings = Strings.tr("Localizable", "Map settings", fallback: "Map settings") + /// A button label. The button allows the user to mark a conversation as read. + public static let markAsRead = Strings.tr("Localizable", "Mark as Read", fallback: "Mark as read") + /// Alert title shown when you have exported your MEGA Recovery Key + public static let masterKeyExported = Strings.tr("Localizable", "masterKeyExported", fallback: "Recovery key exported") + /// 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. + public static let masterKeyExportedAlertMessage = Strings.tr("Localizable", "masterKeyExported_alertMessage", fallback: "The Recovery key has been exported into the Offline section as MEGA-RECOVERYKEY.txt. Note: It will be deleted if you log out, please store it in a safe place.") + /// The title for my message in a chat. The message was sent from yourself. + public static let me = Strings.tr("Localizable", "me", fallback: "Me") + /// Section header of the settings that contains the folder to which photos will be uploded in camera upload + public static let megaCameraUploadsFolder = Strings.tr("Localizable", "MEGA CAMERA UPLOADS FOLDER", fallback: "MEGA Camera uploads folder") + /// Title of the label where the MEGAchat SDK version is shown + public static let megachatSdkVersion = Strings.tr("Localizable", "megachatSdkVersion", fallback: "MEGA Chat SDK version") + /// Label for any ‘Message’ button, link, text, title, etc. - (String as short as possible). + public static let message = Strings.tr("Localizable", "Message", fallback: "Message") + /// Accessibility message when a message is cancelled in chat + public static let messageSendingCancelled = Strings.tr("Localizable", "message sending cancelled", fallback: "message sending cancelled") + /// Accessibility message when a message is sent in chat + public static let messageSent = Strings.tr("Localizable", "message sent", fallback: "message sent") + /// Alert message shown when users try to stream an unsupported audio/video file + public static let messageFileNotSupported = Strings.tr("Localizable", "message_fileNotSupported", fallback: "You can save it for Offline and open it in a compatible app.") + /// Success message shown after forwarding messages to other chats + public static let messagesSent = Strings.tr("Localizable", "messagesSent", fallback: "Messages sent") + /// 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 + public static let microphonePermissions = Strings.tr("Localizable", "microphonePermissions", fallback: "Please give MEGA permission to access your Microphone in Settings") + /// Label for any ‘Minimal’ button, link, text, title, etc. - (String as short as possible). + public static let minimal = Strings.tr("Localizable", "Minimal", fallback: "Minimal") + /// Title of the notification for a missed call + public static let missedCall = Strings.tr("Localizable", "missedCall", fallback: "Missed call") + /// A hint shown at the bottom of the Send Signup Link dialog to tell users they can edit the provided email. + public static let misspelledEmailAddress = Strings.tr("Localizable", "misspelledEmailAddress", fallback: "If you have misspelled your email address, correct it and tap on “Resend”.") + /// Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings. + public static let mobileDataIsTurnedOff = Strings.tr("Localizable", "Mobile Data is turned off", fallback: "Mobile data is turned off") + /// The Moderator permission level in chat. With moderator permissions a participant can manage the chat. + public static let moderator = Strings.tr("Localizable", "moderator", fallback: "Host") + /// A label for any 'Modified' text or title. For example to show the modification date of a file/folder. + public static let modified = Strings.tr("Localizable", "modified", fallback: "Modified") + /// Title for action to modify the registered phone number. + public static let modifyPhoneNumber = Strings.tr("Localizable", "Modify Phone Number", fallback: "Modify phone number") + /// "Monthly" subscriptions + public static let monthly = Strings.tr("Localizable", "monthly", fallback: "Monthly") + /// Used on scheduling chat history clearing by month e.g. 3 months, 6 months. + public static let months = Strings.tr("Localizable", "months", fallback: "months") + /// Top menu option which opens more menu options in a context menu. + public static let more = Strings.tr("Localizable", "more", fallback: "More") + /// 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. + public static func moreThanTwoUsersAreTyping(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "moreThanTwoUsersAreTyping", p1, fallback: "%1$s [A]and more are typing…[/A]") + } + /// Title for the action that allows you to move a file or folder + public static let move = Strings.tr("Localizable", "move", fallback: "Move") + /// Success message shown when you have moved 1 file and 1 folder + public static let moveFileFolderMessage = Strings.tr("Localizable", "moveFileFolderMessage", fallback: "1 file and 1 folder moved") + /// Success message shown when you have moved 1 file and {1+} folders + public static func moveFileFoldersMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "moveFileFoldersMessage", p1, fallback: "1 file and %d folders moved") + } + /// Success message shown when you have moved 1 file + public static let moveFileMessage = Strings.tr("Localizable", "moveFileMessage", fallback: "1 file moved") + /// Success message shown when you have moved {1+} files and 1 folder + public static func moveFilesFolderMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "moveFilesFolderMessage", p1, fallback: "%d files and 1 folder moved") + } + /// Success message shown when you have moved [A] = {1+} files and [B] = {1+} folders + public static let moveFilesFoldersMessage = Strings.tr("Localizable", "moveFilesFoldersMessage", fallback: "[A] files and [B] folders moved") + /// Success message shown when you have moved {1+} files + public static func moveFilesMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "moveFilesMessage", p1, fallback: "%d files moved") + } + /// Success message shown when you have moved 1 folder + public static let moveFolderMessage = Strings.tr("Localizable", "moveFolderMessage", fallback: "1 folder moved") + /// Success message shown when you have moved {1+} folders + public static func moveFoldersMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "moveFoldersMessage", p1, fallback: "%d folders moved") + } + /// SDK error returned when a MFA is required to perform an operation + public static let multiFactorAuthenticationRequired = Strings.tr("Localizable", "Multi-factor authentication required", fallback: "Two-factor authentication required") + /// A button label. The button allows the user to mute a conversation. + public static let mute = Strings.tr("Localizable", "mute", fallback: "Mute") + /// Chat Notifications DND: Title bar message for the dnd activate options + public static let muteChatNotificationsFor = Strings.tr("Localizable", "Mute chat Notifications for", fallback: "Mute chat notifications for") + /// The avatar picture button to go to my account page. Use for voice over. + public static let myAccount = Strings.tr("Localizable", "My Account", fallback: "My account") + /// Destination folder name of chat files + public static let myChatFiles = Strings.tr("Localizable", "My chat files", fallback: "My chat files") + /// Column header of my contacts/chats at copy dialog + public static let myChats = Strings.tr("Localizable", "My chats", fallback: "My chats") + /// Title of the label in the my account section. It shows the credentials of the current user so it can be used to be verified by other contacts + public static let myCredentials = Strings.tr("Localizable", "My credentials", fallback: "My credentials") + /// Label for any ‘My QR code’ button, link, text, title, etc. - (String as short as possible). + public static let myQRCode = Strings.tr("Localizable", "My QR code", fallback: "My QR code") + /// Sort by option (1/6). This one orders the files alphabethically + public static let nameAscending = Strings.tr("Localizable", "nameAscending", fallback: "Name (ascending)") + /// Sort by option (2/6). This one arranges the files on reverse alphabethical order + public static let nameDescending = Strings.tr("Localizable", "nameDescending", fallback: "Name (descending)") + /// Error text shown when you have not entered a correct name + public static let nameInvalidFormat = Strings.tr("Localizable", "nameInvalidFormat", fallback: "Enter a valid name") + /// Error message when a user attempts to change their email without an active login session. + public static let needToBeLoggedInToCompleteYourEmailChange = Strings.tr("Localizable", "needToBeLoggedInToCompleteYourEmailChange", fallback: "You need to be logged in to complete your email change. Please log in again with your current email address, then tap on your confirmation link again.") + /// + public static let never = Strings.tr("Localizable", "never", fallback: "Never") + /// Label shown inside an unseen notification + public static let new = Strings.tr("Localizable", "New", fallback: "New") + /// Title for new camera upload feature + public static let newCameraUpload = Strings.tr("Localizable", "New Camera Upload!", fallback: "New Camera uploads options") + /// Text button for init a group chat with link. + public static let newChatLink = Strings.tr("Localizable", "New Chat Link", fallback: "New chat link") + /// Text button for init a group chat + public static let newGroupChat = Strings.tr("Localizable", "New Group Chat", fallback: "New group chat") + /// Menu option from the `Add` section that allows the user to create a new text file and upload it directly to MEGA. + public static let newTextFile = Strings.tr("Localizable", "new_text_file", fallback: "New text file") + /// Hint text to suggest that the user have to write the new email on it + public static let newEmail = Strings.tr("Localizable", "newEmail", fallback: "New email address") + /// Sort by option (5/6). This one order the files by its modification date, newer first + public static let newest = Strings.tr("Localizable", "newest", fallback: "Newest") + /// Menu option from the `Add` section that allows you to create a 'New Folder' + public static let newFolder = Strings.tr("Localizable", "newFolder", fallback: "New folder") + /// Hint text shown on the create folder alert. + public static let newFolderMessage = Strings.tr("Localizable", "newFolderMessage", fallback: "Name for the new folder") + /// Label in a button that allows to jump to the latest message + public static let newMessages = Strings.tr("Localizable", "newMessages", fallback: "New messages") + /// Hint text to suggest that the user have to write the new password on it + public static let newPassword = Strings.tr("Localizable", "newPassword", fallback: "New password") + /// Notification text body shown when you have received a new shared folder + public static let newSharedFolder = Strings.tr("Localizable", "newSharedFolder", fallback: "New shared folder") + /// + public static let next = Strings.tr("Localizable", "next", fallback: "Next") + /// Label for any ‘Night’ button, link, text, title, etc. - (String as short as possible). + public static let night = Strings.tr("Localizable", "Night", fallback: "Night") + /// + public static let no = Strings.tr("Localizable", "no", fallback: "No") + /// Audio Explorer Screen: No audio files in the account + public static let noAudioFilesFound = Strings.tr("Localizable", "No audio files found", fallback: "No audio files found") + /// In some cases, a user may try to get the link for a chat room, but if such is not set by an operator - it would say "not link available" and not auto create it. + public static let noChatLinkAvailable = Strings.tr("Localizable", "No chat link available.", fallback: "No chat link available.") + /// Photo Explorer Screen: No documents in the account + public static let noDocumentsFound = Strings.tr("Localizable", "No documents found", fallback: "No documents found") + /// Label to show that an SDK operation has been complete successfully. + public static let noError = Strings.tr("Localizable", "No error", fallback: "No error") + /// Text describing that there is not any node marked as favourite + public static let noFavourites = Strings.tr("Localizable", "No Favourites", fallback: "No favourites") + /// Empty Gifs section + public static let noGIFsFound = Strings.tr("Localizable", "No GIFs found", fallback: "No GIFs found") + /// There are no notifications to display. + public static let noNotifications = Strings.tr("Localizable", "No notifications", fallback: "No notifications") + /// Used in Photos app browser. Shown when there are no photos or videos in the Photos app. + public static let noPhotosOrVideos = Strings.tr("Localizable", "No Photos or Videos", fallback: "No photos or videos") + /// Label to indicate not to use any proxy. String as short as possible. + public static let noProxy = Strings.tr("Localizable", "No proxy", fallback: "No proxy") + /// Title for empty state view of "Links" in Shared Items. + public static let noPublicLinks = Strings.tr("Localizable", "No Public Links", fallback: "No public links") + /// Message shown when the user has not recent activity in their account. + public static let noRecentActivity = Strings.tr("Localizable", "No recent activity", fallback: "No recent activity") + /// Title shown when there are no shared files + public static let noSharedFiles = Strings.tr("Localizable", "No Shared Files", fallback: "No shared files") + /// Video Explorer Screen: No audio files in the account + public static let noVideosFound = Strings.tr("Localizable", "No videos found", fallback: "No videos found") + /// Title of empty state view for archived chats. + public static let noArchivedChats = Strings.tr("Localizable", "noArchivedChats", fallback: "No archived chats or meetings") + /// Error message shown when there's no camera available on the device + public static let noCamera = Strings.tr("Localizable", "noCamera", fallback: "No camera available") + /// Information if there are no history messages in current chat conversation + public static let noConversationHistory = Strings.tr("Localizable", "noConversationHistory", fallback: "No conversation history") + /// Empty Conversations section + public static let noConversations = Strings.tr("Localizable", "noConversations", fallback: "No conversations") + /// Title shown inside an alert if you don't have enough space on your device to download something + public static let nodeTooBig = Strings.tr("Localizable", "nodeTooBig", fallback: "You need to free some space on your device") + /// Text shown when you want to send feedback of the app and you don't have an email account set up on your device + public static let noEmailAccountConfigured = Strings.tr("Localizable", "noEmailAccountConfigured", fallback: "No email account set up on your device") + /// Title shown when there's no incoming Shared Items + public static let noIncomingSharedItemsEmptyStateText = Strings.tr("Localizable", "noIncomingSharedItemsEmptyState_text", fallback: "No incoming shared folders") + /// Text shown on the app when you don't have connection to the internet or when you have lost it + public static let noInternetConnection = Strings.tr("Localizable", "noInternetConnection", fallback: "No Internet connection") + /// Add contacts and share dialog error message when user try to add your own email address + public static let noNeedToAddYourOwnEmailAddress = Strings.tr("Localizable", "noNeedToAddYourOwnEmailAddress", fallback: "There’s no need to add your own email address") + /// Title shown when there's no outgoing Shared Items + public static let noOutgoingSharedItemsEmptyStateText = Strings.tr("Localizable", "noOutgoingSharedItemsEmptyState_text", fallback: "No outgoing shared folders") + /// Title shown when there's no pending contact requests + public static let noRequestPending = Strings.tr("Localizable", "noRequestPending", fallback: "No pending requests") + /// Title shown when you make a search and there is 'No Results' + public static let noResults = Strings.tr("Localizable", "noResults", fallback: "No results") + /// Label shown when current account does not have enough quota to complete the operation + public static let notEnoughQuota = Strings.tr("Localizable", "Not enough quota", fallback: "Not enough quota") + /// Label to show that an error related with a resource Not found occurs during a SDK operation. + public static let notFound = Strings.tr("Localizable", "Not found", fallback: "Not found") + /// + public static let notifications = Strings.tr("Localizable", "notifications", fallback: "Notifications") + /// Chat Notifications DND: Option that does not deactivate DND automatically + public static let notificationsMuted = Strings.tr("Localizable", "Notifications muted", fallback: "Notifications muted") + /// Chat Notifications DND: Remaining time left if until today + public static func notificationsWillBeSilencedUntil(_ p1: Any) -> String { + return Strings.tr("Localizable", "Notifications will be silenced until %@", String(describing: p1), fallback: "Notifications will be muted until %@") + } + /// Chat Notifications DND: Remaining time left if until tomorrow + public static func notificationsWillBeSilencedUntilTomorrow(_ p1: Any) -> String { + return Strings.tr("Localizable", "Notifications will be silenced until tomorrow, %@", String(describing: p1), fallback: "Notifications will be muted until tomorrow, %@") + } + /// Text indicating to the user that some action will be postpone. E.g. used for 'rich previews' and management of disk storage. + public static let notNow = Strings.tr("Localizable", "notNow", fallback: "Not now") + /// Location Usage Description. In order to protect user's privacy, Apple requires a specific string explaining why location will be accessed. + public static let nsLocationWhenInUseUsageDescription = Strings.tr("Localizable", "NSLocationWhenInUseUsageDescription", fallback: "MEGA accesses your location when you share it with your contacts in chat.") + /// Users previewing a public chat + public static let observers = Strings.tr("Localizable", "Observers", fallback: "Observers") + /// State shown to if something is enabled or disabled on Settings main tab. For example: "Passcode Off" + public static let off = Strings.tr("Localizable", "off", fallback: "Off") + /// Title of the Offline section + public static let offline = Strings.tr("Localizable", "offline", fallback: "Offline") + /// Title shown when the Offline section is empty, when you don't have download any files. Keep the upper. + public static let offlineEmptyStateTitle = Strings.tr("Localizable", "offlineEmptyState_title", fallback: "No files saved for Offline") + /// Button title to accept something + public static let ok = Strings.tr("Localizable", "ok", fallback: "OK") + /// Error message shown when the users tryes to change his/her email and writes the current one as the new one. + public static let oldAndNewEmailMatch = Strings.tr("Localizable", "oldAndNewEmailMatch", fallback: "The new and the old email must not match") + /// Sort by option (6/6). This one order the files by its modification date, older first + public static let oldest = Strings.tr("Localizable", "oldest", fallback: "Oldest") + /// State shown to if something is enabled or disabled on Settings main tab. For example: "Passcode On" + public static let on = Strings.tr("Localizable", "on", fallback: "On") + /// Used within the `Retention History` dropdown -- available option for the time range selection. + public static let oneDay = Strings.tr("Localizable", "One Day", fallback: "One day") + /// Used within the `Retention History` dropdown -- available option for the time range selection. + public static let oneMonth = Strings.tr("Localizable", "One Month", fallback: "One month") + /// Used within the `Retention History` dropdown -- available option for the time range selection. + public static let oneWeek = Strings.tr("Localizable", "One Week", fallback: "One week") + /// + public static let oneContact = Strings.tr("Localizable", "oneContact", fallback: "1 contact") + /// Subtitle shown on folders that gives you information about its content. This case "{1} file" + public static func oneFile(_ p1: Int) -> String { + return Strings.tr("Localizable", "oneFile", p1, fallback: "%d file") + } + /// Subtitle shown on the Contacts section under the name of the contact you have shared one folder with + public static let oneFolderShared = Strings.tr("Localizable", "oneFolderShared", fallback: "1 folder") + /// Title shown on the Camera Uploads section when the edit mode is enabled and you have selected one photo + public static func oneItemSelected(_ p1: Int) -> String { + return Strings.tr("Localizable", "oneItemSelected", p1, fallback: "%lu item selected") + } + /// Text to inform the user there is an active call and is not participating + public static let ongoingCall = Strings.tr("Localizable", "Ongoing Call", fallback: "Ongoing call") + /// + public static let online = Strings.tr("Localizable", "online", fallback: "Online") + /// Footer description when upload videos for Live Photos is disabled + public static let onlyThePhotoInEachLivePhotoWillBeUploaded = Strings.tr("Localizable", "Only the photo in each Live Photo will be uploaded.", fallback: "Only the photo version of each Live Photo will be uploaded.") + /// Footer description when upload all burst photos is disabled + public static let onlyTheRepresentativePhotosFromYourBurstPhotoSequencesWillBeUploaded = Strings.tr("Localizable", "Only the representative photos from your burst photo sequences will be uploaded.", fallback: "Only favourite photos from your burst photo sequences will be uploaded.") + /// "Title header that refers to where do you do the action 'Empty Rubbish Bin' inside 'Settings' -> 'Advanced' section" + public static let onMEGA = Strings.tr("Localizable", "onMEGA", fallback: "On MEGA") + /// Title header that refers to where do you do the actions 'Clear Offlines files' and 'Clear cache' inside 'Settings' -> 'Advanced' section" + public static let onYourDevice = Strings.tr("Localizable", "onYourDevice", fallback: "On your device") + /// Text indicating the user to open the device settings for MEGA + public static let openSettings = Strings.tr("Localizable", "Open Settings", fallback: "Open Settings") + /// Button title to allow the user open the default browser + public static let openBrowser = Strings.tr("Localizable", "openBrowser", fallback: "Open browser") + /// Button title to trigger the action of opening the file without downloading or opening it. + public static let openButton = Strings.tr("Localizable", "openButton", fallback: "Open") + /// Title shown under the action that allows you to open a file in another app + public static let openIn = Strings.tr("Localizable", "openIn", fallback: "Open in…") + /// Text shown when you try to use a MEGA extension in iOS and you aren't logged + public static let openMEGAAndSignInToContinue = Strings.tr("Localizable", "openMEGAAndSignInToContinue", fallback: "Open MEGA and sign in to continue") + /// Header to explain that 'Upload videos', 'Use cellular connection' and 'Only when charging' are options of the Camera Uploads + public static let options = Strings.tr("Localizable", "options", fallback: "Options") + /// Description text about options when exporting links for several nodes + public static let optionsSuchAsSendDecryptionKeySeparatelySetExpiryDateOrPasswordsAreOnlyAvailableForSingleItems = Strings.tr("Localizable", "Options such as Send Decryption Key Separately, Set Expiry Date or Passwords are only available for single items.", fallback: "Options such as Send decryption key separately, Set expiry date or Set password are only available for single items.") + /// A user can mark a folder or file with its own colour, in this case "Orange + public static let orange = Strings.tr("Localizable", "Orange", fallback: "Orange") + /// Label to show that an error of Out of range occurs during a SDK operation. + public static let outOfRange = Strings.tr("Localizable", "Out of range", fallback: "Out of range") + /// Title of the "Outgoing" Shared Items. + public static let outgoing = Strings.tr("Localizable", "outgoing", fallback: "Outgoing") + /// Label to show that an error related with an over quota occurs during a SDK operation. + public static let overQuota = Strings.tr("Localizable", "Over quota", fallback: "Over quota") + /// Text shown when switching from thumbnail view to page view when previewing a document, for example a PDF. + public static let pageView = Strings.tr("Localizable", "Page View", fallback: "Page view") + /// Headline for parking an account (basically restarting from scratch) + public static let parkAccount = Strings.tr("Localizable", "parkAccount", fallback: "Park account") + /// Label to describe the section where you can see the participants of a group chat + public static let participants = Strings.tr("Localizable", "participants", fallback: "Participants") + /// + public static let passcode = Strings.tr("Localizable", "passcode", fallback: "Passcode") + /// Button text to change the passcode type. + public static let passcodeOptions = Strings.tr("Localizable", "Passcode Options", fallback: "Passcode options") + /// Message shown when the password has been copied to the Clipboard + public static let passwordCopiedToClipboard = Strings.tr("Localizable", "Password Copied to Clipboard", fallback: "Password copied to clipboard") + /// Text for options in Get Link View to activate password protection + public static let passwordProtection = Strings.tr("Localizable", "Password Protection", fallback: "Password protection") + /// Title for feature Password Reminder + public static let passwordReminder = Strings.tr("Localizable", "Password Reminder", fallback: "Password reminder") + /// 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. + public static let passwordAccepted = Strings.tr("Localizable", "passwordAccepted", fallback: "Password accepted") + /// Success message shown when the password has been changed + public static let passwordChanged = Strings.tr("Localizable", "passwordChanged", fallback: "Your password has been changed.") + /// + public static let passwordGood = Strings.tr("Localizable", "passwordGood", fallback: "This password will withstand most typical brute-force attacks. Please ensure that you will remember it.") + /// Message shown when the user enters a wrong password + public static let passwordInvalidFormat = Strings.tr("Localizable", "passwordInvalidFormat", fallback: "Enter a valid password") + /// + public static let passwordMedium = Strings.tr("Localizable", "passwordMedium", fallback: "Your password is good enough to proceed, but it is recommended to strengthen your password further.") + /// Hint text to suggest that the user has to write his password + public static let passwordPlaceholder = Strings.tr("Localizable", "passwordPlaceholder", fallback: "Password") + /// Headline of the password reset recovery procedure + public static let passwordReset = Strings.tr("Localizable", "passwordReset", fallback: "Password reset") + /// Error text shown when you have not written the same password + public static let passwordsDoNotMatch = Strings.tr("Localizable", "passwordsDoNotMatch", fallback: "Passwords do not match") + /// Label displayed during checking the strength of the password introduced. Represents Medium security + public static let passwordStrengthMedium = Strings.tr("Localizable", "PasswordStrengthMedium", fallback: "Medium") + /// + public static let passwordStrong = Strings.tr("Localizable", "passwordStrong", fallback: "This password will withstand most sophisticated brute-force attacks. Please ensure that you will remember it.") + /// + public static let passwordVeryWeakOrWeak = Strings.tr("Localizable", "passwordVeryWeakOrWeak", fallback: "Your password is easily guessed. Try making your password longer. Combine uppercase and lowercase letters. Add special characters. Do not use names or dictionary words.") + /// Error text shown when you introduce a wrong password on the confirmation proccess + public static let passwordWrong = Strings.tr("Localizable", "passwordWrong", fallback: "Wrong password") + /// tool bar title used in transfer widget, allow user to resume all transfers in the list + public static let pauseAll = Strings.tr("Localizable", "Pause All", fallback: "Pause all") + /// A label to indicate a paused state for a transfer item (upload/download). + public static let paused = Strings.tr("Localizable", "paused", fallback: "Paused") + /// The header of a notification related to payments + public static let paymentInfo = Strings.tr("Localizable", "Payment info", fallback: "Payment info") + /// Business expired account Overdue payment page header. + public static let paymentOverdue = Strings.tr("Localizable", "Payment overdue", fallback: "Payment overdue") + /// Label shown when a contact request is pending + public static let pending = Strings.tr("Localizable", "pending", fallback: "Pending") + /// Text explaining users how the chat links work. + public static let peopleCanJoinYourGroupByUsingThisLink = Strings.tr("Localizable", "People can join your group by using this link.", fallback: "People can join your group by using this link.") + /// Per folder configuration. For example the options for 'Sorting Preference' in the app are: 'Per Folder' and 'Same for All'. + public static let perFolder = Strings.tr("Localizable", "Per Folder", fallback: "Per folder") + /// Message to notify user the file version will be permanently removed + public static let permanentlyRemoved = Strings.tr("Localizable", "permanentlyRemoved", fallback: "It will be permanently removed") + /// Error message shown when you are trying to do an action with a file or folder and you don't have the necessary permissions + public static let permissionMessage = Strings.tr("Localizable", "permissionMessage", fallback: "Check your permissions on this folder") + /// Title of the view that shows the kind of permissions (Read Only, Read & Write or Full Access) that you can give to a shared folder + public static let permissions = Strings.tr("Localizable", "permissions", fallback: "Permissions") + /// Message shown when you have changed the permissions of a shared folder + public static let permissionsChanged = Strings.tr("Localizable", "permissionsChanged", fallback: "Permissions changed") + /// Error title shown when you are trying to do an action with a file or folder and you don't have the necessary permissions + public static let permissionTitle = Strings.tr("Localizable", "permissionTitle", fallback: "Permission error") + /// Text related to verified phone number. Used as title or cell description. + public static let phoneNumber = Strings.tr("Localizable", "Phone Number", fallback: "Phone Number") + /// Alert message to explain that the MEGA app needs permission to access your device photos + public static let photoLibraryPermissions = Strings.tr("Localizable", "photoLibraryPermissions", fallback: "Please give MEGA permission to access your Photos in Settings") + /// Footer text for Camera Upload switch section when photos and videos upload is enabled + public static let photosAndVideosWillBeUploadedToCameraUploadsFolder = Strings.tr("Localizable", "Photos and videos will be uploaded to Camera Uploads folder.", fallback: "Photos and videos will be uploaded to the Camera uploads folder.") + /// Message shown the pending video files when photos uploaded and video upload is not enabled. Plural. + public static func photosUploadedVideoUploadsAreOffLuVideosNotUploaded(_ p1: Int) -> String { + return Strings.tr("Localizable", "Photos uploaded, video uploads are off, %lu videos not uploaded", p1, fallback: "Photos uploaded, video uploads are off; %lu videos not uploaded") + } + /// Message shown the pending video file when photos uploaded and video upload is not enabled. Singular. + public static let photosUploadedVideoUploadsAreOff1VideoNotUploaded = Strings.tr("Localizable", "Photos uploaded, video uploads are off, 1 video not uploaded", fallback: "Photos uploaded, video uploads are off; 1 video not uploaded") + /// Footer text for Camera Upload switch section when only photos upload is enabled + public static let photosWillBeUploadedToCameraUploadsFolder = Strings.tr("Localizable", "Photos will be uploaded to Camera Uploads folder.", fallback: "Photos will be uploaded to the Camera uploads folder.") + /// Text shown in location-type messages + public static let pinnedLocation = Strings.tr("Localizable", "Pinned Location", fallback: "Pinned location") + /// Title of the section about the plan in the storage tab in My Account Section + public static let plan = Strings.tr("Localizable", "Plan", fallback: "Plan") + /// Section header of Audio Player playlist that contains playing track + public static let playing = Strings.tr("Localizable", "Playing", fallback: "Playing") + /// Title of a dialog in which we request access to a specific permission, like the Location Services + public static let pleaseAllowAccess = Strings.tr("Localizable", "Please allow access", fallback: "Please allow access") + /// Error message if user click next button without enter a valid phone number + public static let pleaseEnterAValidPhoneNumber = Strings.tr("Localizable", "Please enter a valid phone number", fallback: "Please supply a valid phone number.") + /// Detailed explanation of why the user should give permission to access to the photos + public static let pleaseGiveTheMEGAAppPermissionToAccessPhotosToSharePhotosAndVideos = Strings.tr("Localizable", "Please give the MEGA App permission to access Photos to share photos and videos.", fallback: "Please give MEGA permission to access Photos to share photos and videos.") + /// Label tell user to enter received txt to below input boxes + public static let pleaseTypeTheVerificationCodeSentTo = Strings.tr("Localizable", "Please type the verification code sent to", fallback: "Please enter the verification code sent to") + /// A message on the Verify Login page telling the user to enter their 2FA code. + public static let pleaseEnterTheSixDigitCode = Strings.tr("Localizable", "pleaseEnterTheSixDigitCode", fallback: "Please enter the 6-digit code generated by your authenticator app.") + /// A message shown to explain that the user has to input (type or paste) their recovery key to continue with the reset password process. + public static let pleaseEnterYourRecoveryKey = Strings.tr("Localizable", "pleaseEnterYourRecoveryKey", fallback: "Please enter your Recovery key") + /// Alert title shown when you need to log in to continue with the action you want to do + public static let pleaseLogInToYourAccount = Strings.tr("Localizable", "pleaseLogInToYourAccount", fallback: "Please log in to your account.") + /// A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer. + public static let pleaseSaveYourRecoveryKey = Strings.tr("Localizable", "pleaseSaveYourRecoveryKey", fallback: "Please save your Recovery key in a safe location") + /// + public static let pleaseStrengthenYourPassword = Strings.tr("Localizable", "pleaseStrengthenYourPassword", fallback: "Please strengthen your password.") + /// Message body of the email that appears when the users tap on "Send feedback" + public static let pleaseWriteYourFeedback = Strings.tr("Localizable", "pleaseWriteYourFeedback", fallback: "Please write your feedback here:") + /// Label to indicate a server port to be used. String as short as possible. + public static let port = Strings.tr("Localizable", "Port", fallback: "Port") + /// Label for the status of a transfer when is being preparing - (String as short as possible). + public static let preparing = Strings.tr("Localizable", "preparing...", fallback: "Preparing…") + /// Title to preview document + public static let previewContent = Strings.tr("Localizable", "previewContent", fallback: "Preview content") + /// A button label which opens a dialog to display the full version history of the selected file. + public static let previousVersions = Strings.tr("Localizable", "previousVersions", fallback: "Previous versions") + /// Title of one of the Settings sections where you can see the MEGA's 'Privacy Policy' + public static let privacyPolicyLabel = Strings.tr("Localizable", "privacyPolicyLabel", fallback: "Privacy Policy") + /// The new LITE payment plan. This means it is a light or lightweight payment plan compared to the full / heavyweight plans with lots of bandwidth and storage (PRO I, PRO II, PRO III). + public static let proLite = Strings.tr("Localizable", "Pro Lite", fallback: "Pro Lite") + /// A title for a notification saying the user’s pricing plan will expire soon. + public static let proMembershipPlanExpiringSoon = Strings.tr("Localizable", "PRO membership plan expiring soon", fallback: "Pro membership plan expiring soon") + /// Title to confirm that you want to logout + public static let proceedToLogout = Strings.tr("Localizable", "proceedToLogout", fallback: "Proceed to log out") + /// Error message shown when the selected product doesn't exist + public static func productNotFound(_ p1: Any) -> String { + return Strings.tr("Localizable", "productNotFound", String(describing: p1), fallback: "Product %@ is not found, please contact ios@mega.nz") + } + /// Price asociated with the MEGA PRO account level you can subscribe + public static func productPricePerMonth(_ p1: Any) -> String { + return Strings.tr("Localizable", "productPricePerMonth", String(describing: p1), fallback: "%@ per month") + } + /// Label for any 'Profile' button, link, text, title, etc. - (String as short as possible). + public static let profile = Strings.tr("Localizable", "profile", fallback: "Profile") + /// An alert dialog for the Get Link feature + public static let proOnly = Strings.tr("Localizable", "proOnly", fallback: "(Pro only)") + /// Title of the Proxy section under Settings + public static let proxy = Strings.tr("Localizable", "Proxy", fallback: "Proxy") + /// Label to indicate if the proxy used requires a password. String as short as possible. + public static let proxyServerRequiresAPassword = Strings.tr("Localizable", "Proxy server requires a password", fallback: "Proxy server requires a password") + /// Label to indicate the dialog of Proxy Settings. Keep capital letters. + public static let proxySettings = Strings.tr("Localizable", "Proxy Settings", fallback: "Proxy settings") + /// Alert message shown when a purchase was restored succesfully + public static let purchaseRestoreMessage = Strings.tr("Localizable", "purchaseRestore_message", fallback: "Your purchase was restored") + /// A user can mark a folder or file with its own colour, in this case “Purple”. + public static let purple = Strings.tr("Localizable", "Purple", fallback: "Purple") + /// QR Code label, used in Settings as title. String as short as possible + public static let qrCode = Strings.tr("Localizable", "qrCode", fallback: "QR code") + /// Quality title, used in changing the export quality of scaned doc + public static let quality = Strings.tr("Localizable", "Quality", fallback: "Quality") + /// Text shown under the title 'Video quality' that explains what it means + public static let qualityOfVideosUploadedToAChat = Strings.tr("Localizable", "qualityOfVideosUploadedToAChat", fallback: "Quality of videos uploaded to a chat") + /// 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 + public static let queued = Strings.tr("Localizable", "queued", fallback: "Queued") + /// Title for the QuickAccess widget + public static let quickAccess = Strings.tr("Localizable", "Quick Access", fallback: "Quick access") + /// Text description for the Favourites QuickAccess widget + public static let quicklyAccessFilesOnFavouritesSection = Strings.tr("Localizable", "Quickly access files on Favourites section", fallback: "Quickly access files from your Favourites section") + /// Text description for the Offline QuickAccess widget + public static let quicklyAccessFilesOnOfflineSection = Strings.tr("Localizable", "Quickly access files on Offline section", fallback: "Quickly access files from your Offline section") + /// Text description for the Recents QuickAccess widget + public static let quicklyAccessFilesOnRecentsSection = Strings.tr("Localizable", "Quickly access files on Recents section", fallback: "Quickly access files from your Recents section") + /// Text description for the QuickAccess widget + public static let quicklyAccessFilesOnRecentsFavouritesOrOfflineSection = Strings.tr("Localizable", "Quickly access files on Recents, Favourites, or Offline section", fallback: "Quickly access files on Recents, Favourites, or the Offline section") + /// Label to show that the rate limit has been reached during a SDK operation. + public static let rateLimitExceeded = Strings.tr("Localizable", "Rate limit exceeded", fallback: "Rate limit exceeded") + /// Title to rate the app + public static let rateUsLabel = Strings.tr("Localizable", "rateUsLabel", fallback: "Rate us") + /// Label to show that an error related with an read error occurs during a SDK operation. + public static let readError = Strings.tr("Localizable", "Read error", fallback: "Read error") + /// Permissions given to the user you share your folder with + public static let readAndWrite = Strings.tr("Localizable", "readAndWrite", fallback: "Read and write") + /// Permissions given to the user you share your folder with + public static let readOnly = Strings.tr("Localizable", "readOnly", fallback: "Read-only") + /// Title of one of the filters in 'Contacts requests' section. If 'Received' is selected, it will only show the requests which have been recieved. + public static let received = Strings.tr("Localizable", "received", fallback: "Received") + /// Label for any ‘Recently Added’ button, link, text, title, etc. On iOS is used on a section that shows the 'Recently Added' contacts + public static let recentlyAdded = Strings.tr("Localizable", "Recently Added", fallback: "Recently added") + /// Title for the recents section. + public static let recents = Strings.tr("Localizable", "Recents", fallback: "Recents") + /// Title shown when the user lost the connection in a call, and the app will try to reconnect the user again. + public static let reconnecting = Strings.tr("Localizable", "Reconnecting...", fallback: "Reconnecting…") + /// Label indicating that a voice clip is being recorded. String as short as possible. + public static let recording = Strings.tr("Localizable", "Recording...", fallback: "Recording…") + /// 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. + public static let recoveryKey = Strings.tr("Localizable", "recoveryKey", fallback: "Recovery key") + /// Message of the dialog displayed when copy the user's Recovery Key to the clipboard to be saved or exported. + public static let recoveryKeyCopiedToClipboard = Strings.tr("Localizable", "recoveryKeyCopiedToClipboard", fallback: "Recovery key copied to clipboard. Save it to a safe place where you can easily access later.") + /// Message shown during forgot your password process if the link to reset password has expired + public static let recoveryLinkHasExpired = Strings.tr("Localizable", "recoveryLinkHasExpired", fallback: "This recovery link has expired, please try again.") + /// A user can mark a folder or file with its own colour, in this case “Red”. + public static let red = Strings.tr("Localizable", "Red", fallback: "Red") + /// SDK error returned when an operation is rejected due to fraud protection + public static let rejectedByFraudProtection = Strings.tr("Localizable", "Rejected by fraud protection", fallback: "Rejected by fraud protection") + /// A reminder notification to remind the user to respond to the contact request. + public static let reminderYouHaveAContactRequest = Strings.tr("Localizable", "Reminder: You have a contact request", fallback: "Reminder: you have a contact request") + /// Text that describes why the user should test his/her password before logging out + public static let remindPasswordLogoutText = Strings.tr("Localizable", "remindPasswordLogoutText", fallback: "Before logging out we recommend you export your Recovery key to a safe place. Note that you’re not able to reset your password if you forget it.") + /// Text that describes why the user should test his/her password + public static let remindPasswordText = Strings.tr("Localizable", "remindPasswordText", fallback: "Due to MEGA’s encryption technology you are unable to reset your password without data loss. Please make sure you remember your password.") + /// Used as a title in the "Password reminder" dialog, that pop ups to ensure that the user had exported his recovery key OR still remembers his password. + public static let remindPasswordTitle = Strings.tr("Localizable", "remindPasswordTitle", fallback: "Do you remember your password?") + /// Title for the action that allows to remove a file or folder + public static let remove = Strings.tr("Localizable", "remove", fallback: "Remove") + /// Context menu item. Allows user to delete file/folder from favourites + public static let removeFavourite = Strings.tr("Localizable", "Remove Favourite", fallback: "Remove favourite") + /// A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days. + public static let removeFilesOlderThan = Strings.tr("Localizable", "Remove files older than", fallback: "Remove files older than") + /// Option shown on the action sheet where you can choose or change the color label of a file or folder. The 'Remove Label' only appears if you have previously selected a label + public static let removeLabel = Strings.tr("Localizable", "Remove Label", fallback: "Remove label") + /// Edit nickname screen: Remove nickname button title + public static let removeNickname = Strings.tr("Localizable", "Remove Nickname", fallback: "Remove nickname") + /// Text to indicate the user to remove the password of a link + public static let removePassword = Strings.tr("Localizable", "Remove Password", fallback: "Remove password") + /// Title for action to remove the registered phone number. + public static let removePhoneNumber = Strings.tr("Localizable", "Remove Phone Number", fallback: "Remove phone number") + /// Button to remove some photo, e.g. avatar photo. Try to keep the text short (as in English) + public static let removePhoto = Strings.tr("Localizable", "Remove Photo", fallback: "Remove photo") + /// The text in the button to remove all contacts to a shared folder on one click + public static let removeShare = Strings.tr("Localizable", "Remove Share", fallback: "Remove 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. + public static let removedXItemsFromAShare = Strings.tr("Localizable", "Removed [X] items from a share", fallback: "Removed [X] items from a share") + /// Notification when on client side when owner of a shared folder removes folder/file from it. + public static let removedItemFromSharedFolder = Strings.tr("Localizable", "Removed item from shared folder", fallback: "Removed item from shared folder") + /// Success message shown when the selected contact has been removed. 'Contact {Name of contact} removed' + public static func removedContact(_ p1: Any) -> String { + return Strings.tr("Localizable", "removedContact", String(describing: p1), fallback: "Contact %@ removed") + } + /// Alert message shown on the Rubbish Bin when you want to remove "1 file and {1+} folders" + public static func removeFileFoldersToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "removeFileFoldersToRubbishBinMessage", p1, fallback: "You are about to permanently remove 1 file and %d folders. Would you like to proceed? (You cannot undo this action.)") + } + /// Alert message shown on the Rubbish Bin when you want to remove "1 file and 1 folder" + public static let removeFileFolderToRubbishBinMessage = Strings.tr("Localizable", "removeFileFolderToRubbishBinMessage", fallback: "You are about to permanently remove 1 file and 1 folder. Would you like to proceed? (You cannot undo this action.)") + /// Alert message shown on the Rubbish Bin when you want to remove "[A] = {1+} files and [B] = {1+} folders + public static let removeFilesFoldersToRubbishBinMessage = Strings.tr("Localizable", "removeFilesFoldersToRubbishBinMessage", fallback: "You are about to permanently remove [A] files and [B] folders. Would you like to proceed? (You cannot undo this action.)") + /// Alert message shown on the Rubbish Bin when you want to remove "{1+} files and 1 folder" + public static func removeFilesFolderToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "removeFilesFolderToRubbishBinMessage", p1, fallback: "You are about to permanently remove %d files and 1 folder. Would you like to proceed? (You cannot undo this action.)") + } + /// Alert message shown on the Rubbish Bin when you want to remove "{1+} files" + public static func removeFilesToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "removeFilesToRubbishBinMessage", p1, fallback: "You are about to permanently remove %d files. Would you like to proceed? (You cannot undo this action.)") + } + /// Alert message shown on the Rubbish Bin when you want to remove '1 file' + public static let removeFileToRubbishBinMessage = Strings.tr("Localizable", "removeFileToRubbishBinMessage", fallback: "You are about to permanently remove 1 file. Would you like to proceed? (You cannot undo this action.)") + /// Alert message shown on the Rubbish Bin when you want to remove "{1+} folders" + public static func removeFoldersToRubbishBinMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "removeFoldersToRubbishBinMessage", p1, fallback: "You are about to permanently remove %d folders. Would you like to proceed? (You cannot undo this action.)") + } + /// Alert message shown on the Rubbish Bin when you want to remove '1 folder' + public static let removeFolderToRubbishBinMessage = Strings.tr("Localizable", "removeFolderToRubbishBinMessage", fallback: "You are about to permanently remove 1 folder. Would you like to proceed? (You cannot undo this action.)") + /// Alert message shown when the user remove one item from the Offline section + public static let removeItemFromOffline = Strings.tr("Localizable", "removeItemFromOffline", fallback: "Are you sure you want to delete this item from Offline?") + /// Alert message shown when the user remove various items from the Offline section + public static let removeItemsFromOffline = Strings.tr("Localizable", "removeItemsFromOffline", fallback: "Are you sure you want to delete these items from Offline?") + /// Alert message shown on the Shared Items section when you want to remove %d shares + public static func removeMultipleSharesMultipleContactsMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "removeMultipleSharesMultipleContactsMessage", p1, fallback: "Are you sure you want to remove these shares? (Shared with %d contacts)") + } + /// Alert confirmation message shown when you want to remove more than one contact from your contacts list + public static func removeMultipleUsersMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "removeMultipleUsersMessage", p1, fallback: "Are you sure you want to remove %d users from your contact list?") + } + /// Alert title shown on the Rubbish Bin when you want to remove some files and folders of your MEGA account + public static let removeNodeFromRubbishBinTitle = Strings.tr("Localizable", "removeNodeFromRubbishBinTitle", fallback: "Confirm removal") + /// Alert message shown on the Shared Items section when you want to remove 1 share + public static func removeOneShareMultipleContactsMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "removeOneShareMultipleContactsMessage", p1, fallback: "Are you sure you want to remove this share? (Shared with %d contacts)") + } + /// Alert message shown on the Shared Items section when you want to remove 1 share + public static let removeOneShareOneContactMessage = Strings.tr("Localizable", "removeOneShareOneContactMessage", fallback: "Are you sure you want to remove this share? (Shared with 1 contact)") + /// A button title which removes a participant from a chat. + public static let removeParticipant = Strings.tr("Localizable", "removeParticipant", fallback: "Remove participant") + /// 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). + public static let removePreview = Strings.tr("Localizable", "removePreview", fallback: "Remove preview") + /// Alert title shown on the Shared Items section when you want to remove 1 share + public static let removeSharing = Strings.tr("Localizable", "removeSharing", fallback: "Remove sharing") + /// Alert title shown when you want to remove one or more contacts + public static let removeUserTitle = Strings.tr("Localizable", "removeUserTitle", fallback: "Remove contact") + /// Title for the action that allows you to rename a file or folder + public static let rename = Strings.tr("Localizable", "rename", fallback: "Rename") + /// Alert title to ask if user want to rename the file. %@ is a place holder. + public static func renameFileAlertTitle(_ p1: Any) -> String { + return Strings.tr("Localizable", "rename_file_alert_title", String(describing: p1), fallback: "Rename file %@?") + } + /// The title of a menu button which allows users to rename a group chat. + public static let renameGroup = Strings.tr("Localizable", "renameGroup", fallback: "Rename group") + /// Hint text to suggest that the user have to write the new name for the file or folder + public static let renameNodeMessage = Strings.tr("Localizable", "renameNodeMessage", fallback: "Enter the new name") + /// Label for the ‘Renews on’ text into the my account page, indicating the renewal date of a subscription - (String as short as possible). + public static let renewsOn = Strings.tr("Localizable", "Renews on", fallback: "Renews on") + /// Title for the action that allows you to replace a file. + public static let replace = Strings.tr("Localizable", "replace", fallback: "Replace") + /// Label to show that a request error occurs during a SDK operation. + public static let requestFailedRetrying = Strings.tr("Localizable", "Request failed, retrying", fallback: "Request failed, retrying") + /// Success message shown when you acepted a contact request + public static let requestAccepted = Strings.tr("Localizable", "requestAccepted", fallback: "Request accepted") + /// Button on the Pro page to request a custom Pro plan because their storage usage is more than the regular plans. + public static let requestAPlan = Strings.tr("Localizable", "requestAPlan", fallback: "Request a plan") + /// Success message shown when you Cancelled a contact request + public static let requestCancelled = Strings.tr("Localizable", "requestCancelled", fallback: "Request cancelled") + /// Success message shown when you remove a contact request + public static let requestDeleted = Strings.tr("Localizable", "requestDeleted", fallback: "Request deleted") + /// Label for any ‘Requests’ button, link, text, title, etc. On iOS is used to go to the Contact request section from Contacts + public static let requests = Strings.tr("Localizable", "Requests", fallback: "Requests") + /// Label indicating that the passcode (pin) view will be displayed if the application goes back to foreground after being x time in background. Examples: require passcode immediately, require passcode after 5 minutes. + public static let requirePasscode = Strings.tr("Localizable", "Require Passcode", fallback: "Require passcode") + /// A button to resend the email confirmation. + public static let resend = Strings.tr("Localizable", "resend", fallback: "Resend") + /// Button to reset the password + public static let reset = Strings.tr("Localizable", "reset", fallback: "Reset") + /// Text to indicate the user to reset/change the password of a link + public static let resetPassword = Strings.tr("Localizable", "Reset Password", fallback: "Reset password") + /// Action to reset the current valid QR code of the user + public static let resetQrCode = Strings.tr("Localizable", "resetQrCode", fallback: "Reset QR code") + /// Footer that explains what would happen if the user resets his/her QR code + public static let resetQrCodeFooter = Strings.tr("Localizable", "resetQrCodeFooter", fallback: "Previous QR codes will no longer be valid.") + /// A label for the Restart button to relaunch MEGAsync. + public static let restart = Strings.tr("Localizable", "restart", fallback: "Restart") + /// Button title to restore failed purchases + public static let restore = Strings.tr("Localizable", "restore", fallback: "Restore") + /// + public static let resume = Strings.tr("Localizable", "resume", fallback: "Resume") + /// tool bar title used in transfer widget, allow user to Pause all transfers in the list + public static let resumeAll = Strings.tr("Localizable", "Resume All", fallback: "Resume all") + /// Alert header for asking permission to resume transfers + public static let resumeTransfers = Strings.tr("Localizable", "Resume Transfers?", fallback: "Resume transfers?") + /// Button which allows to retry send message in chat conversation. + public static let retry = Strings.tr("Localizable", "retry", fallback: "Retry") + /// Label for the state of a transfer when is being retrying - (String as short as possible). + public static let retrying = Strings.tr("Localizable", "Retrying...", fallback: "Retrying…") + /// A button label which reverts a certain version of a file to be the current version of the selected file. + public static let revert = Strings.tr("Localizable", "revert", fallback: "Revert") + /// 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. + public static let richPreviewsConfirmation = Strings.tr("Localizable", "richPreviewsConfirmation", fallback: "You are disabling rich URL previews permanently. You can re-enable rich URL previews in your settings. Do you want to proceed?") + /// 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. + public static let richPreviewsFooter = Strings.tr("Localizable", "richPreviewsFooter", fallback: "Enhance the MEGA Chat experience. URL content will be retrieved without zero-knowledge encryption.") + /// Title used in settings that enables the generation of link previews in the chat + public static let richUrlPreviews = Strings.tr("Localizable", "richUrlPreviews", fallback: "Rich URL previews") + /// title of a field to show the role or position (you can use whichever is best for translation) of the user in business accounts + public static let role = Strings.tr("Localizable", "Role:", fallback: "Role:") + /// Title for the Rubbish-Bin Cleaning Scheduler feature + public static let rubbishBinCleaningScheduler = Strings.tr("Localizable", "Rubbish-Bin Cleaning Scheduler:", fallback: "Rubbish bin emptying scheduler") + /// Title of one of the Settings sections where you can see your MEGA 'Rubbish Bin' + public static let rubbishBinLabel = Strings.tr("Localizable", "rubbishBinLabel", fallback: "Rubbish bin") + /// Same for all configuration. For example the options for 'Sorting Preference' in the app are: 'Per Folder' and 'Same for all Folders'. + public static let sameForAll = Strings.tr("Localizable", "Same for All", fallback: "Same for all") + /// Button title to 'Save' the selected option + public static let save = Strings.tr("Localizable", "save", fallback: "Save") + /// Footer text shown under the Camera setting to explain the option 'Save in Photos' + public static let saveACopyOfTheImagesAndVideosTakenFromTheMEGAAppInYourDeviceSMediaLibrary = Strings.tr("Localizable", "Save a copy of the images and videos taken from the MEGA app in your device’s media library.", fallback: "Save a copy of the images and videos taken from the MEGA App in your device’s media library.") + /// Header to introduce how to deal with HEIC format photos in Camera Uplaod + public static let saveHeicPhotosAs = Strings.tr("Localizable", "SAVE HEIC PHOTOS AS", fallback: "Save HEIC photos as") + /// Header to introduce how to deal with HEVC format videos in Camera Uplaod + public static let saveHevcVideosAs = Strings.tr("Localizable", "SAVE HEVC VIDEOS AS", fallback: "Save HEVC videos as") + /// Settings section title where you can enable the option to 'Save Images in Photos' + public static let saveImagesInPhotos = Strings.tr("Localizable", "Save Images in Photos", fallback: "Save images 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 + public static let saveInPhotos = Strings.tr("Localizable", "Save in Photos", fallback: "Save in Photos") + /// Setting title for Doc scan view + public static let saveSettings = Strings.tr("Localizable", "Save Settings", fallback: "Save settings") + /// A button label which allows the users save images/videos in the Photos app. + public static let saveToPhotos = Strings.tr("Localizable", "Save to Photos", fallback: "Save to Photos") + /// Settings section title where you can enable the option to 'Save Videos in Photos' + public static let saveVideosInPhotos = Strings.tr("Localizable", "Save Videos in Photos", fallback: "Save videos in Photos") + /// Footer shown to remenber that if you select a yearly plan yo will save up to 17% + public static let save17 = Strings.tr("Localizable", "save17", fallback: "Save 16%") + /// State shown if something is 'Saved' (String as short as possible). + public static let saved = Strings.tr("Localizable", "saved", fallback: "Saved") + /// Text shown when a photo or video is saved to Photos app + public static let savedToPhotos = Strings.tr("Localizable", "Saved to Photos", fallback: "Saved to Photos") + /// Title shown under the action that allows you to save an image to your camera roll + public static let saveImage = Strings.tr("Localizable", "saveImage", fallback: "Save image") + /// Text shown when starting the process to save a photo or video to Photos app + public static let savingToPhotos = Strings.tr("Localizable", "Saving to Photos…", fallback: "Saving to Photos…") + /// Menu option from the `Add` section that allows the user to scan document and upload it directly to MEGA. + public static let scanDocument = Strings.tr("Localizable", "Scan Document", fallback: "Scan document") + /// Segmented control title for view that allows the user to scan QR codes. String as short as possible. + public static let scanCode = Strings.tr("Localizable", "scanCode", fallback: "Scan code") + /// A message on the setup two-factor authentication page on the mobile web client. + public static let scanOrCopyTheSeed = Strings.tr("Localizable", "scanOrCopyTheSeed", fallback: "Scan or copy the seed to your authenticator app. Be sure to back up this seed to a safe place in case you lose your device.") + /// Title of the label where the SDK version is shown + public static let sdkVersion = Strings.tr("Localizable", "sdkVersion", fallback: "MEGA SDK version") + /// Title of the Spotlight Search section + public static let search = Strings.tr("Localizable", "Search", fallback: "Search") + /// This is the placeholder text for GIPHY search + public static let searchGIPHY = Strings.tr("Localizable", "Search GIPHY", fallback: "Search GIPHY") + /// Search placeholder text in search bar on home screen + public static let searchYourFiles = Strings.tr("Localizable", "Search Your Files", fallback: "Search your files") + /// Description shown in a page of the onboarding screens explaining the encryption paradigm + public static let securityIsWhyWeExistYourFilesAreSafeWithUsBehindAWellOiledEncryptionMachineWhereOnlyYouCanAccessYourFiles = Strings.tr("Localizable", "Security is why we exist, your files are safe with us behind a well oiled encryption machine where only you can access your files.", fallback: "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 of one of the Settings sections where you can configure "Security Options" of your MEGA account + public static let securityOptions = Strings.tr("Localizable", "securityOptions", fallback: "Security options") + /// Button title to see the available pro plans in MEGA + public static let seePlans = Strings.tr("Localizable", "seePlans", fallback: "See plans") + /// Button that allows you to select something (a folder, a message...) + public static let select = Strings.tr("Localizable", "select", fallback: "Select") + /// Action button text of folder selection screen for camera uploads + public static let selectFolder = Strings.tr("Localizable", "Select Folder", fallback: "Select folder") + /// Text shown to explain how and where you can invite friends + public static let selectFromPhoneContactsOrEnterMultipleEmailAddresses = Strings.tr("Localizable", "Select from phone contacts or enter multiple email addresses", fallback: "Select from device contacts or enter multiple email addresses.") + /// Footer text explaining what means choosing a view mode preference 'Per Folder', 'List view' or 'Thumbnail view' in Settings - Appearance - Sorting And View Mode. + public static let selectViewModeListOrThumbnailOnAPerFolderBasisOrUseTheSameViewModeForAllFolders = Strings.tr("Localizable", "Select view mode (List or Thumbnail) on a per-folder basis, or use the same view mode for all folders.", fallback: "Select view mode (List or Thumbnail) on a per-folder basis, or use the same view mode for all folders.") + /// Select all items/elements on the list + public static let selectAll = Strings.tr("Localizable", "selectAll", fallback: "Select all") + /// Title shown on the navigation bar to explain that you have to choose a destination for the files and/or folders in case you copy, move, import or do some action with them. + public static let selectDestination = Strings.tr("Localizable", "selectDestination", fallback: "Select destination") + /// Text of the button for user to select files in MEGA. + public static let selectFiles = Strings.tr("Localizable", "selectFiles", fallback: "Select files") + /// Header shown to help on the purchasin process + public static let selectMembership = Strings.tr("Localizable", "selectMembership", fallback: "Select membership:") + /// Text that explains that you have to select the plan you want and/or need after creating an account + public static let selectOneAccountType = Strings.tr("Localizable", "selectOneAccountType", fallback: "Select one account type:") + /// Title shown on the Camera Uploads section when the edit mode is enabled. On this mode you can select photos + public static let selectTitle = Strings.tr("Localizable", "selectTitle", fallback: "Select items") + /// Label for any 'Send' button, link, text, title, etc. - (String as short as possible). + public static let send = Strings.tr("Localizable", "send", fallback: "Send") + /// Used in Photos app browser view to send the photos from the view to the chat. + public static func sendD(_ p1: Int) -> String { + return Strings.tr("Localizable", "Send (%d)", p1, fallback: "Send (%d)") + } + /// Text for options in Get Link View to separate the key from the link + public static let sendDecryptionKeySeparately = Strings.tr("Localizable", "Send Decryption Key Separately", fallback: "Send decryption key separately") + /// Giphy section header + public static let sendGIF = Strings.tr("Localizable", "Send GIF", fallback: "Send GIF") + /// Alert title shown when the user opens a shared Geolocation for the first time from any client, we will show a confirmation dialog warning the user that he is now leaving the E2EE paradigm. + public static let sendLocation = Strings.tr("Localizable", "Send Location", fallback: "Send location") + /// Description of High Image Quality option + public static let sendOriginalSizeIncreasedQualityImages = Strings.tr("Localizable", "Send original size, increased quality images", fallback: "Always send original size images.") + /// Description of Optimised Image Quality option + public static let sendSmallerSizeImagesOptimisedForLowerDataConsumption = Strings.tr("Localizable", "Send smaller size images optimised for lower data consumption", fallback: "Always send optimised images.") + /// Description of Automatic Image Quality option + public static let sendSmallerSizeImagesThroughCellularNetworksAndOriginalSizeImagesThroughWifi = Strings.tr("Localizable", "Send smaller size images through cellular networks and original size images through wifi", fallback: "Send optimised images when on mobile data but original size images when on Wi-Fi.") + /// Title of the button to share a location in a chat + public static let sendThisLocation = Strings.tr("Localizable", "Send This Location", fallback: "Send this location") + /// A button label. The button sends contact information to a user in the conversation. + public static let sendContact = Strings.tr("Localizable", "sendContact", fallback: "Send contacts") + /// Title of one of the Settings sections where you can 'Send Feedback' to MEGA + public static let sendFeedbackLabel = Strings.tr("Localizable", "sendFeedbackLabel", fallback: "Send feedback") + /// Title to perform the action of sending a message to a contact. + public static let sendMessage = Strings.tr("Localizable", "sendMessage", fallback: "Send message") + /// 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. + public static let sent = Strings.tr("Localizable", "sent", fallback: "Sent") + /// When a contact sent a contact/friend request + public static let sentYouAContactRequest = Strings.tr("Localizable", "Sent you a contact request", fallback: "Sent you a contact request") + /// A summary message when a user sent a contact's details through the chat. Please keep %s as it will be replaced at runtime with the name of the contact that was sent. + public static func sentContact(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "sentContact", p1, fallback: "Sent contact: %s") + } + /// 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. + public static func sentXContacts(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "sentXContacts", p1, fallback: "Sent %s contacts.") + } + /// Label to indicate the server IP to be used. String as short as possible. + public static let server = Strings.tr("Localizable", "Server:", fallback: "Server:") + /// Message shown when the app is waiting for the server to complete a request due to a HTTP error 500. + public static let serversAreTooBusy = Strings.tr("Localizable", "serversAreTooBusy", fallback: "Servers are too busy. Please wait…") + /// 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. + public static let sessionsClosed = Strings.tr("Localizable", "sessionsClosed", fallback: "Sessions closed") + /// Contact details screen: Set the alias(nickname) for a user + public static let setNickname = Strings.tr("Localizable", "Set Nickname", fallback: "Set nickname") + /// Text for options in Get Link View to set password protection + public static let setPassword = Strings.tr("Localizable", "Set Password", fallback: "Set password") + /// A label in the Get Link dialog which allows the user to set an expiry date on their public link. + public static let setExpiryDate = Strings.tr("Localizable", "setExpiryDate", fallback: "Set expiry date") + /// This is a title label on the Export Link dialog. The title covers the section where the user can password protect a public link. + public static let setPasswordProtection = Strings.tr("Localizable", "setPasswordProtection", fallback: "Set password protection") + /// Navigation path for Storage settings in iOS + public static func settingsGeneralStorage(_ p1: Any) -> String { + return Strings.tr("Localizable", "Settings > General > %@ Storage", String(describing: p1), fallback: "Settings > General > %@ Storage") + } + /// Title of the Settings section + public static let settingsTitle = Strings.tr("Localizable", "settingsTitle", fallback: "Settings") + /// Button which triggers the initial setup + public static let setupMEGA = Strings.tr("Localizable", "Setup MEGA", fallback: "Set up MEGA") + /// Title of the screen that shows the users with whom the user can share a folder + public static let shareWith = Strings.tr("Localizable", "Share with", fallback: "Share with") + /// Title of the tab bar item for the Shared Items section + public static let shared = Strings.tr("Localizable", "shared", fallback: "Shared") + /// Footer description for Upload Shared Albums title when option enabled + public static let sharedAlbumsFromYourDeviceSPhotosAppWillBeUploaded = Strings.tr("Localizable", "Shared Albums from your device's Photos app will be uploaded.", fallback: "Shared Albums from your device’s Photos app will be uploaded.") + /// Footer description for Upload Shared Albums title when option disabled + public static let sharedAlbumsFromYourDeviceSPhotosAppWillNotBeUploaded = Strings.tr("Localizable", "Shared Albums from your device's Photos app will not be uploaded.", fallback: "Shared albums from your device’s Photos app will not be uploaded.") + /// Header of block with all shared files in chat. + public static let sharedFiles = Strings.tr("Localizable", "Shared Files", fallback: "Shared files") + /// Success message shown when the user has successfully shared something + public static let sharedSuccessfully = Strings.tr("Localizable", "Shared successfully", fallback: "Shared") + /// Message shown when a folder have been shared + public static let sharedFolderSuccess = Strings.tr("Localizable", "sharedFolder_success", fallback: "Folder shared") + /// Title of the incoming shared folders of a user. + public static let sharedFolders = Strings.tr("Localizable", "sharedFolders", fallback: "Shared folders") + /// Success message for sharing multiple folders. + public static func sharedFoldersSuccess(_ p1: Int) -> String { + return Strings.tr("Localizable", "sharedFolders_success", p1, fallback: "%d folders shared") + } + /// Title of Shared Items section + public static let sharedItems = Strings.tr("Localizable", "sharedItems", fallback: "Shared items") + /// title of the screen that shows the users with whom the user has shared a folder + public static let sharedWidth = Strings.tr("Localizable", "sharedWidth", fallback: "Shared with:") + /// Title of the view where you see with who you have shared a folder + public static let sharedWith = Strings.tr("Localizable", "sharedWith", fallback: "Shared with") + /// Text shown to explain with how many contacts you have shared a folder + public static func sharedWithXContacts(_ p1: Int) -> String { + return Strings.tr("Localizable", "sharedWithXContacts", p1, fallback: "Shared with %d contacts") + } + /// Inform user that there were unsupported assets in the share extension + public static let shareExtensionUnsupportedAssets = Strings.tr("Localizable", "shareExtensionUnsupportedAssets", fallback: "Some items could not be shared with MEGA") + /// Message shown when a share has been left + public static let shareLeft = Strings.tr("Localizable", "shareLeft", fallback: "Share left") + /// Message shown when a share have been removed + public static let shareRemoved = Strings.tr("Localizable", "shareRemoved", fallback: "Share removed") + /// Message shown when some shares have been left + public static let sharesLeft = Strings.tr("Localizable", "sharesLeft", fallback: "Shares left") + /// Message shown when some shares have been removed + public static let sharesRemoved = Strings.tr("Localizable", "sharesRemoved", fallback: "Shares removed") + /// Item menu option upon right click on one or multiple files. + public static let sharing = Strings.tr("Localizable", "sharing", fallback: "Sharing") + /// Text title for shortcuts, ie in the widget + public static let shortcuts = Strings.tr("Localizable", "Shortcuts", fallback: "Shortcuts") + /// Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...' + public static let show = Strings.tr("Localizable", "Show", fallback: "Show") + /// Title to describe a simple (four digit) passcode + public static let simplePasscodeLabel = Strings.tr("Localizable", "simplePasscodeLabel", fallback: "Simple passcode") + /// "Size" of the file or folder you are sharing + public static let size = Strings.tr("Localizable", "size", fallback: "Size") + /// Button title that skips the current action + public static let skipButton = Strings.tr("Localizable", "skipButton", fallback: "Skip") + /// Sort by option (4/6). This one order the files by its size, in this case from smaller to bigger size + public static let smallest = Strings.tr("Localizable", "smallest", fallback: "Smallest") + /// + public static let somethingWentWrong = Strings.tr("Localizable", "Something went wrong", fallback: "Something went wrong") + /// Inside of Settings - Appearance, there is a view on which you can change the sorting preferences or the view mode preference for the app. + public static let sortingAndViewMode = Strings.tr("Localizable", "Sorting And View Mode", fallback: "Sorting and view mode") + /// Section title of the 'Sorting And View Mode' view inside of Settings - Appearence - Sorting And View Mode. + public static let sortingPreference = Strings.tr("Localizable", "Sorting preference", fallback: "Sorting preference") + /// Section title of the 'Sort by' + public static let sortTitle = Strings.tr("Localizable", "sortTitle", fallback: "Sort by") + /// Error shown when SSL check has failed + public static let sslVerificationFailed = Strings.tr("Localizable", "SSL verification failed", fallback: "SSL verification failed") + /// Alert title shown when the app detects that the Secure Sockets Layer (SSL) key of MEGA can't be verified. + public static let sslUnverifiedAlertTitle = Strings.tr("Localizable", "sslUnverified_alertTitle", fallback: "MEGA is unable to connect securely through SSL. You might be on public Wi-Fi with additional requirements.") + /// The Standard permission level in chat. With the standard permissions a participant can read and type messages in a chat. + public static let standard = Strings.tr("Localizable", "standard", fallback: "Standard") + /// Empty Conversations description + public static let startChattingSecurelyWithYourContactsUsingEndToEndEncryption = Strings.tr("Localizable", "Start chatting securely with your contacts using end-to-end encryption", fallback: "Start chatting securely with your contacts using zero-knowledge encryption") + /// Menu option from the `Add` section that allows the user to Start Group. + public static let startGroup = Strings.tr("Localizable", "Start Group", fallback: "Start group") + /// start a chat/conversation + public static let startConversation = Strings.tr("Localizable", "startConversation", fallback: "Start conversation") + /// 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") + public static let startingFreshAccount = Strings.tr("Localizable", "startingFreshAccount", fallback: "I acknowledge that I am starting a fresh, empty account and that I will lose all data in my present account unless I recall my password or locate an exported Recovery key.") + /// Caption of the button to proceed + public static let startNewAccount = Strings.tr("Localizable", "startNewAccount", fallback: "Start new account") + /// Title that refers to the status of the chat (Either Online or Offline) + public static let status = Strings.tr("Localizable", "status", fallback: "Status") + /// Label title to enable/disable the feature of the chat status that mantains your chosen status even if you don't have connected devices + public static let statusPersistence = Strings.tr("Localizable", "statusPersistence", fallback: "Status persistence") + /// Label for any ‘Storage’ button, link, text, title, etc. - (String as short as possible). + public static let storage = Strings.tr("Localizable", "Storage", fallback: "Storage") + /// Over Disk Quota Screen Navigation Bar Title + public static let storageFull = Strings.tr("Localizable", "Storage Full", fallback: "Storage full") + /// A header/title of a section which contains information about used/available storage space on a user's cloud drive. + public static let storageQuota = Strings.tr("Localizable", "storageQuota", fallback: "Storage quota") + /// Label displayed during checking the strength of the password introduced. Represents Strong security + public static let strong = Strings.tr("Localizable", "strong", fallback: "Strong") + /// A message shown at registration time when users have to select their plan. The FREE 50 GB plan is subject to the achievements program. + public static let subjectToYourParticipationInOurAchievementsProgram = Strings.tr("Localizable", "subjectToYourParticipationInOurAchievementsProgram", fallback: "Subject to your participation in our achievements program.") + /// Footer description for upload synced albums title + public static let syncedAlbumsAreWhereYouSyncPhotosOrVideosToYourDeviceSPhotosAppFromITunes = Strings.tr("Localizable", "Synced albums are where you sync photos or videos to your device's Photos app from iTunes.", fallback: "Synced albums are where you sync photos or videos to your device’s Photos app from iTunes.") + /// The header of a notification indicating that a file or folder has been taken down due to infringement or other reason. + public static let takedownNotice = Strings.tr("Localizable", "Takedown notice", fallback: "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. + public static let takedownReinstated = Strings.tr("Localizable", "Takedown reinstated", fallback: "Takedown reinstated") + /// Stand-alone error message shown to users who attempt to load/access a link where the link has been taken down due to severe violation of our terms of service. + public static let takenDownDueToSevereViolationOfOurTermsOfService = Strings.tr("Localizable", "Taken down due to severe violation of our terms of service", fallback: "This folder or file was reported to contain objectionable content, such as Child Exploitation Material, Violent Extremism, or Bestiality. The link creator’s account has been closed and their full details, including IP address, have been provided to the authorities.") + /// Message shown when the app is waiting for the server to complete a request due to an API lock (error -3). + public static let takingLongerThanExpected = Strings.tr("Localizable", "takingLongerThanExpected", fallback: "The process is taking longer than expected. Please wait…") + /// Tooltip shown when the user presses but does not hold the microphone icon to send a voice clip + public static func tapAndHoldToRecordReleaseToSend(_ p1: Any) -> String { + return Strings.tr("Localizable", "Tap and hold %@ to record, release to send", String(describing: p1), fallback: "Tap and hold %@ to record, release to send") + } + /// Subtitle shown in a chat to inform where to tap to enter in the chat details view + public static let tapHereForInfo = Strings.tr("Localizable", "Tap here for info", fallback: "Tap here for info") + /// Text showing the user how to write more than one email in order to invite them to MEGA + public static let tapSpaceToEnterMultipleEmails = Strings.tr("Localizable", "Tap space to enter multiple emails", fallback: "Tap space to enter multiple emails") + /// Text hint to let the user know that tapping something will be copied into the pasteboard + public static let tapToCopy = Strings.tr("Localizable", "Tap to Copy", fallback: "Tap to copy") + /// Message shown in a chat room for a one on one call + public static let tapToReturnToCall = Strings.tr("Localizable", "Tap to return to call", fallback: "Tap to return to call") + /// Footer shown in the Share Extension telling that a file can be renamed if tapped + public static let tapFileToRename = Strings.tr("Localizable", "tapFileToRename", fallback: "Tap file to rename") + /// Label to show that an error related with a temporary problem occurs during a SDK operation. + public static let temporarilyNotAvailable = Strings.tr("Localizable", "Temporarily not available", fallback: "Temporarily not available") + /// Error shown when terms of service are breached during download. + public static let termsOfServiceBreached = Strings.tr("Localizable", "Terms of Service breached", fallback: "Terms of Service breached") + /// Error text shown when you don't have selected the checkbox to agree with the Terms of Service + public static let termsCheckboxUnselected = Strings.tr("Localizable", "termsCheckboxUnselected", fallback: "You need to agree with the Terms of Service to register an account on MEGA.") + /// This error is shown in the account creation page. User has to agree to the terms and conditions. If the user does not agree to the losing password results in data loss condition then this error is shown + public static let termsForLosingPasswordCheckboxUnselected = Strings.tr("Localizable", "termsForLosingPasswordCheckboxUnselected", fallback: "You need to agree that you understand the danger of losing your password") + /// Title of one of the Settings sections where you can see the MEGA's 'Terms of Service' + public static let termsOfServicesLabel = Strings.tr("Localizable", "termsOfServicesLabel", fallback: "Terms of Service") + /// Label for test password button and titles + public static let testPassword = Strings.tr("Localizable", "testPassword", fallback: "Test password") + /// 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. + public static let testPasswordLogoutText = Strings.tr("Localizable", "testPasswordLogoutText", fallback: "You are about to log out, please test your password to ensure you remember it. If you lose your password, you will lose access to your MEGA data.") + /// 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. + public static let testPasswordText = Strings.tr("Localizable", "testPasswordText", fallback: "Please test your password below to ensure you remember it. If you lose your password, you will lose access to your MEGA data. [A]Learn more[/A]") + /// Alert title shown when the user has purchase correctly some plan + public static let thankYouTitle = Strings.tr("Localizable", "thankYou_title", fallback: "Thank you") + /// An error message which is shown when you open a file/folder link (or other shared resource) and it’s no longer available because the user account that created the link has been terminated due to multiple violations of our Terms of Service + public static let theAccountThatCreatedThisLinkHasBeenTerminatedDueToMultipleViolationsOfOurATermsOfServiceA = Strings.tr("Localizable", "The account that created this link has been terminated due to multiple violations of our [A]Terms of Service[/A].", fallback: "The account that created this link has been terminated due to multiple violations of our [A]Terms of Service[/A].") + /// Dialog title for the no enough device storage screen + public static let theDeviceDoesNotHaveEnoughSpaceForMEGAToRunProperly = Strings.tr("Localizable", "The device does not have enough space for MEGA to run properly.", fallback: "The device does not have enough space for MEGA to run properly.") + /// Footer description when upload Hidden Album is enabled + public static let theHiddenAlbumIsWhereYouHidePhotosOrVideosInYourDevicePhotosApp = Strings.tr("Localizable", "The Hidden Album is where you hide photos or videos in your device Photos app.", fallback: "The Hidden album is where you hide photos or videos in your device’s Photos app.") + /// Error message that will be shown when the code introduced do not match with the one you received + public static let theVerificationCodeDoesnTMatch = Strings.tr("Localizable", "The verification code doesn't match.", fallback: "The verification code doesn’t match.") + /// Footer description when upload videos for Live Photos is enabled + public static let theVideoAndThePhotoInEachLivePhotoWillBeUploaded = Strings.tr("Localizable", "The video and the photo in each Live Photo will be uploaded.", fallback: "The photo and video versions of each Live photo will be uploaded.") + /// Message shown when you're trying to open a file or folder from the mobile webclient and the accounts logged are not the same + public static let theContentIsNotAvailableForThisAccount = Strings.tr("Localizable", "theContentIsNotAvailableForThisAccount", fallback: "The content you are trying to access is not available for this account. Please try to log in with same account as in the mobile web client.") + /// Add contacts and share dialog error message when user try to add wrong email address + public static let theEmailAddressFormatIsInvalid = Strings.tr("Localizable", "theEmailAddressFormatIsInvalid", fallback: "The email address format is invalid") + /// A tooltip message which shows when a file name is duplicated during renaming. + public static let thereIsAlreadyAFileWithTheSameName = Strings.tr("Localizable", "There is already a file with the same name", fallback: "There is already a file with the same name") + /// A tooltip message which is shown when a folder name is duplicated during renaming or creation. + public static let thereIsAlreadyAFolderWithTheSameName = Strings.tr("Localizable", "There is already a folder with the same name", fallback: "There is already a folder with the same name") + /// Success message shown when some contacts have been invited + public static let theUsersHaveBeenInvited = Strings.tr("Localizable", "theUsersHaveBeenInvited", fallback: "The users have been invited and will appear in your contact list once accepted.") + /// Error message shown to user when a copy/import operation would take them over their storage limit. + public static let thisActionCanNotBeCompletedAsItWouldTakeYouOverYourCurrentStorageLimit = Strings.tr("Localizable", "This action can not be completed as it would take you over your current storage limit", fallback: "This action cannot be completed as it would take you over your current storage limit") + /// Shown when an inexisting/unavailable/removed link is tried to be opened. + public static let thisChatLinkIsNoLongerAvailable = Strings.tr("Localizable", "This chat link is no longer available", fallback: "This chat link is no longer available") + /// Popup notification text on mouse-over of taken down file. + public static let thisFileHasBeenTheSubjectOfATakedownNotice = Strings.tr("Localizable", "This file has been the subject of a takedown notice.", fallback: "This file has been the subject of a takedown notice.") + /// Popup notification text on mouse-over taken down folder. + public static let thisFolderHasBeenTheSubjectOfATakedownNotice = Strings.tr("Localizable", "This folder has been the subject of a takedown notice.", fallback: "This folder has been the subject of a takedown notice.") + /// Stand-alone error message shown to users who attempt to load/access a link where the user has been suspended/taken-down due to severe violation of our terms of service. + public static let thisLinkIsUnavailableAsTheUserSAccountHasBeenClosedForGrossViolationOfMEGASATermsOfServiceA = Strings.tr("Localizable", "This link is unavailable as the user’s account has been closed for gross violation of MEGA’s [A]Terms of Service[/A].", fallback: "This link is unavailable as the user’s account has been closed for gross violation of MEGA’s [A]Terms of Service[/A].") + /// Message shown when the user opens a shared Geolocation for the first time from any client, we will show a confirmation dialog warning the user that he is now leaving the E2EE paradigm. + public static let thisLocationWillBeOpenedUsingAThirdPartyMapsProviderOutsideTheEndToEndEncryptedMEGAPlatform = Strings.tr("Localizable", "This location will be opened using a third party maps provider outside the end-to-end encrypted MEGA platform.", fallback: "This location will be opened using a third party maps provider outside the end-to-end encrypted MEGA platform.") + /// Shows the error when the limit of reactions per message is reached and a user tries to add one more. Keep the placeholder because is to show limit number in runtime. + public static func thisMessageHasReachedTheMaximumLimitOfDReactions(_ p1: Int) -> String { + return Strings.tr("Localizable", "This message has reached the maximum limit of %d reactions.", p1, fallback: "This message has reached the maximum limit of %d reactions.") + } + /// Error message that will show to user when host detected that the mobile number has been registered already + public static let thisNumberIsAlreadyAssociatedWithAMegaAccount = Strings.tr("Localizable", "This number is already associated with a Mega account", fallback: "This number is already associated with a MEGA account.") + /// Message for action to modify the registered phone number. + public static let thisOperationWillRemoveYourCurrentPhoneNumberAndStartTheProcessOfAssociatingANewPhoneNumberWithYourAccount = Strings.tr("Localizable", "This operation will remove your current phone number and start the process of associating a new phone number with your account.", fallback: "This operation will remove your current phone number and start the process of associating a new phone number with your account.") + /// A log message in a chat to indicate that the message has been deleted by the user. + public static let thisMessageHasBeenDeleted = Strings.tr("Localizable", "thisMessageHasBeenDeleted", fallback: "This message has been deleted") + /// Text shown for switching from list view to thumbnail view. + public static let thumbnailView = Strings.tr("Localizable", "Thumbnail View", fallback: "Thumbnail view") + /// This dialog message is used on the Password Decrypt dialog. The link is a password protected link so the user needs to enter the password to decrypt the link. + public static let toAccessThisLinkYouWillNeedItsPassword = Strings.tr("Localizable", "To access this link, you will need its password.", fallback: "To access this link, you will need its password.") + /// Shown as an error message when the user selects "create chat link", but haven't entered a title for the newly created room + public static let toCreateAChatLinkYouMustNameTheGroup = Strings.tr("Localizable", "To create a chat link you must name the group.", fallback: "To create a chat link you must name the group.") + /// Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user + public static let toDisableTheRubbishBinCleaningSchedulerOrSetALongerRetentionPeriodYouNeedToSubscribeToAPROPlan = Strings.tr("Localizable", "To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan.", fallback: "To disable the Rubbish bin emptying scheduler or set a longer retention period, you need to subscribe to a Pro plan.") + /// Detailed explanation of why the user should give some permissions to MEGA + public static let toFullyTakeAdvantageOfYourMEGAAccountWeNeedToAskYouSomePermissions = Strings.tr("Localizable", "To fully take advantage of your MEGA account we need to ask you some permissions.", fallback: "To fully take advantage of your MEGA account we need to ask you some permissions.") + /// Detailed explanation of why the user should give permission to access to the camera and the microphone + public static let toMakeEncryptedVoiceAndVideoCallsAllowMEGAAccessToYourCameraAndMicrophone = Strings.tr("Localizable", "To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone", fallback: "To send voice messages and make encrypted voice and video calls, allow MEGA access to your Camera and Microphone") + /// When user is on PRO 3 plan, we will display an extra label to notify user that they can still contact support to have a customised plan. + public static let toUpgradeYourCurrentSubscriptionPleaseContactSupportForAACustomPlanA = Strings.tr("Localizable", "To upgrade your current subscription, please contact support for a [A]custom plan[/A].", fallback: "To upgrade your current subscription, please contact support for a [A]custom plan[/A].") + /// + public static let today = Strings.tr("Localizable", "Today", fallback: "Today") + /// Label to show that an error for multiple concurrent connections or transfers occurs during a SDK operation. + public static let tooManyConcurrentConnectionsOrTransfers = Strings.tr("Localizable", "Too many concurrent connections or transfers", fallback: "Too many concurrent connections or transfers") + /// SDK error returned when there are too many requests + public static let tooManyRequests = Strings.tr("Localizable", "Too many requests", fallback: "Too many requests") + /// Error message when to many attempts to login + public static func tooManyAttemptsLogin(_ p1: Any) -> String { + return Strings.tr("Localizable", "tooManyAttemptsLogin", String(describing: p1), fallback: "You have attempted to log in too many times. Please wait until %@ and try again.") + } + /// Message shown when the app is waiting for the server to complete a request due to a rate limit (error -4). + public static let tooManyRequest = Strings.tr("Localizable", "tooManyRequest", fallback: "Too many requests. Please wait.") + /// A title message in the user’s account settings for showing the storage used for file versions. + public static let totalSizeTakenUpByFileVersions = Strings.tr("Localizable", "Total size taken up by file versions:", fallback: "Total size taken up by file versions:") + /// label for the total file size of multiple files and/or folders (no need to put the colon punctuation in the translation) + public static let totalSize = Strings.tr("Localizable", "totalSize", fallback: "Total size") + /// Label to indicate the amount of transfer quota in several places. It is a ‘noun‘ and there is an screenshot with an use example - (String as short as possible). + public static let transfer = Strings.tr("Localizable", "Transfer", fallback: "Transfer") + /// Notification message shown when a transfer failed. Keep colon. + public static let transferFailed = Strings.tr("Localizable", "Transfer failed:", fallback: "Transfer failed:") + /// Label indicating transfer over quota. + public static let transferOverQuota = Strings.tr("Localizable", "Transfer over quota", fallback: "Transfer quota exceeded") + /// Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'. + public static let transferQuota = Strings.tr("Localizable", "Transfer Quota", fallback: "Transfer") + /// Success message shown when one transfer has been cancelled + public static let transferCancelled = Strings.tr("Localizable", "transferCancelled", fallback: "Transfer cancelled") + /// Title of the Transfers section + public static let transfers = Strings.tr("Localizable", "transfers", fallback: "Transfers") + /// Success message shown when all the transfers have been cancelled + public static let transfersCancelled = Strings.tr("Localizable", "transfersCancelled", fallback: "Transfers cancelled") + /// Title shown when the there is not any transfer and they are not paused + public static let transfersEmptyStateTitleAll = Strings.tr("Localizable", "transfersEmptyState_titleAll", fallback: "No transfers") + /// Title shown when the there is not any transfer, and the filter "Download" option is selected + public static let transfersEmptyStateTitleDownload = Strings.tr("Localizable", "transfersEmptyState_titleDownload", fallback: "No downloads") + /// Title shown when the transfers are paused + public static let transfersEmptyStateTitlePaused = Strings.tr("Localizable", "transfersEmptyState_titlePaused", fallback: "Paused transfers") + /// Title shown when the there is not any transfer, and the filter "Upload" option is selected + public static let transfersEmptyStateTitleUpload = Strings.tr("Localizable", "transfersEmptyState_titleUpload", fallback: "No uploads") + /// Footer text that explains when disabling the HTTP protocol for transfers may be useful + public static let transfersSectionFooter = Strings.tr("Localizable", "transfersSectionFooter", fallback: "Enable this option only if your transfers don’t start. In normal circumstances HTTP is satisfactory as all transfers are already encrypted.") + /// Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app. + public static let turnMobileDataOn = Strings.tr("Localizable", "Turn Mobile Data on", fallback: "Turn mobile data on") + /// A title for the Two-Factor Authentication section on the My Account - Security page. + public static let twoFactorAuthentication = Strings.tr("Localizable", "twoFactorAuthentication", fallback: "Two-factor authentication") + /// A message on a dialog to say that 2FA has been successfully disabled. + public static let twoFactorAuthenticationDisabled = Strings.tr("Localizable", "twoFactorAuthenticationDisabled", fallback: "Two-factor authentication disabled") + /// A title on the mobile web client page showing that 2FA has been enabled successfully. + public static let twoFactorAuthenticationEnabled = Strings.tr("Localizable", "twoFactorAuthenticationEnabled", fallback: "Two-factor authentication enabled") + /// A message on the dialog shown after 2FA was successfully enabled. + public static let twoFactorAuthenticationEnabledDescription = Strings.tr("Localizable", "twoFactorAuthenticationEnabledDescription", fallback: "Next time you log in to your account you will be asked to enter a 6-digit code provided by your authenticator app.") + /// An informational message on the Backup Recovery Key dialog. + public static let twoFactorAuthenticationEnabledWarning = Strings.tr("Localizable", "twoFactorAuthenticationEnabledWarning", fallback: "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.") + /// Text shown in the purchase plan view to explain that annual subscription is 17% cheaper than 12 monthly payments + public static func twoMonthsFree(_ p1: CChar) -> String { + return Strings.tr("Localizable", "twoMonthsFree", p1, fallback: "An annual subscription is 16% cheaper than 12 monthly payments") + } + /// 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. + public static func twoUsersAreTyping(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "twoUsersAreTyping", p1, fallback: "%1$s [A]are typing…[/A]") + } + /// Refers to the type of a file or folder. + public static let type = Strings.tr("Localizable", "type", fallback: "Type") + /// Error shown when trying to start a call in a group with more peers than allowed + public static let unableToStartACallBecauseTheParticipantsLimitWasExceeded = Strings.tr("Localizable", "Unable to start a call because the participants limit was exceeded.", fallback: "Unable to start the call due to the participant limit having been exceeded.") + /// Message shown when the app is waiting for the server to complete a request due to connectivity issue. + public static let unableToReachMega = Strings.tr("Localizable", "unableToReachMega", fallback: "Unable to reach MEGA. Please check your connectivity or try again later.") + /// The title of the dialog to unarchive an archived chat. + public static let unarchiveChat = Strings.tr("Localizable", "unarchiveChat", fallback: "Unarchive") + /// Confirmation message for user to confirm it will unarchive an archived chat. + public static let unarchiveChatMessage = Strings.tr("Localizable", "unarchiveChatMessage", fallback: "Are you sure you want to unarchive this conversation?") + /// Text used to show the user that some resource is not available + public static let unavailable = Strings.tr("Localizable", "Unavailable", fallback: "Unavailable") + /// Label to show that an error related with an unknown error occurs during a SDK operation. + public static let unknownError = Strings.tr("Localizable", "Unknown error", fallback: "Unknown error") + /// Header of block with achievements bonuses. + public static let unlockedBonuses = Strings.tr("Localizable", "unlockedBonuses", fallback: "Unlocked bonuses:") + /// A button label. The button allows the user to unmute a conversation. + public static let unmute = Strings.tr("Localizable", "unmute", fallback: "Unmute") + /// Used in Photos app browser carousel view to unselect a selected photo. + public static let unselect = Strings.tr("Localizable", "Unselect", fallback: "Unselect") + /// Chat Notifications DND: Option to turn the DND on forever + public static let untilITurnItBackOn = Strings.tr("Localizable", "Until I turn it back on", fallback: "Until I turn them back on") + /// Chat Notifications DND: Option to turn the DND on until this morning 8 AM + public static let untilThisMorning = Strings.tr("Localizable", "Until this morning", fallback: "Until this morning") + /// Chat Notifications DND: Option to turn the DND on until tomorrow morning 8 AM + public static let untilTomorrowMorning = Strings.tr("Localizable", "Until tomorrow morning", fallback: "Until tomorrow morning") + /// Caption of a button to upgrade the account to Pro status + public static let upgrade = Strings.tr("Localizable", "upgrade", fallback: "Upgrade") + /// Mail title to upgrade to a custom plan + public static let upgradeToACustomPlan = Strings.tr("Localizable", "Upgrade to a custom plan", fallback: "Upgrade to a custom plan") + /// Title of a warning recommending upgrade to Pro + public static let upgradeToPro = Strings.tr("Localizable", "Upgrade to Pro", fallback: "Upgrade to Pro") + /// Button title which triggers the action to upgrade your MEGA account level + public static let upgradeAccount = Strings.tr("Localizable", "upgradeAccount", fallback: "Upgrade account") + /// + public static let upload = Strings.tr("Localizable", "upload", fallback: "Upload") + /// Used in Photos app browser view to upload the photos from the view to the cloud. + public static func uploadD(_ p1: Int) -> String { + return Strings.tr("Localizable", "Upload (%d)", p1, fallback: "Upload (%d)") + } + /// Title of the switch to config whether to upload synced albums + public static let uploadAlbumsSyncedFromITunes = Strings.tr("Localizable", "Upload Albums Synced from iTunes", fallback: "Upload albums synced from iTunes") + /// Title of the switch to config whether to upload all burst photos + public static let uploadAllBurstPhotos = Strings.tr("Localizable", "Upload All Burst Photos", fallback: "Upload all burst photos") + /// Text to indicate the action of uploading a file to the cloud drive + public static let uploadFile = Strings.tr("Localizable", "Upload File", fallback: "Upload file") + /// Title of the switch to config whether to upload Hidden Album + public static let uploadHiddenAlbum = Strings.tr("Localizable", "Upload Hidden Album", fallback: "Upload Hidden album") + /// Message shown when camera upload paused because of no WiFi. Plural. + public static func uploadPausedBecauseOfNoWiFiLuFilesPending(_ p1: Int) -> String { + return Strings.tr("Localizable", "Upload paused because of no WiFi, %lu files pending", p1, fallback: "Upload paused due to no Wi-Fi connection, %lu files pending") + } + /// Message shown when camera upload paused because of no WiFi. Singular. + public static let uploadPausedBecauseOfNoWiFi1FilePending = Strings.tr("Localizable", "Upload paused because of no WiFi, 1 file pending", fallback: "Upload paused due to no Wi-Fi connection, 1 file pending") + /// SDK error returned when an upload would produce recursivity + public static let uploadProducesRecursivity = Strings.tr("Localizable", "Upload produces recursivity", fallback: "Transfer failed due to a recursive directory structure") + /// Title of the switch to config whether to upload Shared Albums + public static let uploadSharedAlbums = Strings.tr("Localizable", "Upload Shared Albums", fallback: "Upload shared albums") + /// Title of the switch to config whether to upload videos for Live Photos + public static let uploadVideosForLivePhotos = Strings.tr("Localizable", "Upload Videos for Live Photos", fallback: "Upload videos for Live photos") + /// Title of one of the filters in the Transfers section. In this case "Uploads" transfers. + public static let uploads = Strings.tr("Localizable", "uploads", fallback: "Uploads") + /// Message shown when a upload starts + public static let uploadStartedMessage = Strings.tr("Localizable", "uploadStarted_Message", fallback: "Upload started") + /// + public static let uploadToMega = Strings.tr("Localizable", "uploadToMega", fallback: "Upload to MEGA") + /// Option title to enable upload videos with Camera Uploads + public static let uploadVideosLabel = Strings.tr("Localizable", "uploadVideosLabel", fallback: "Upload videos") + /// Title next to a switch button (On-Off) to allow using mobile data (Roaming) for videos. + public static let useMobileDataForVideos = Strings.tr("Localizable", "Use Mobile Data for Videos", fallback: "Use mobile data for videos") + /// Title for an action to use most compatible formats + public static let useMostCompatibleFormats = Strings.tr("Localizable", "Use Most Compatible Formats", fallback: "Use most compatible formats") + /// Title next to a switch button (On-Off) to allow using mobile data (Roaming) for a feature. + public static let useMobileData = Strings.tr("Localizable", "useMobileData", fallback: "Use mobile data") + /// user (singular) label indicating is receiving some info, for example shared folders + public static let user = Strings.tr("Localizable", "user", fallback: "user") + /// Label presented to Admins that full management of the business is only available in a desktop web browser + public static let userManagementIsOnlyAvailableFromADesktopWebBrowser = Strings.tr("Localizable", "User management is only available from a desktop web browser.", fallback: "User management is only available from a desktop web browser.") + /// Label to indicate the username of the proxy. String as short as possible. + public static let username = Strings.tr("Localizable", "Username:", fallback: "Username:") + /// used for example when a folder is shared with 2 or more users + public static let users = Strings.tr("Localizable", "users", fallback: "users") + /// Button title + public static let verified = Strings.tr("Localizable", "verified", fallback: "Approved") + /// Label for any ‘Verify’ button, link, text, title, etc. - (String as short as possible). + public static let verify = Strings.tr("Localizable", "verify", fallback: "Approve") + /// Verify your account title + public static let verifyYourAccount = Strings.tr("Localizable", "Verify Your Account", fallback: "Verify your account") + /// 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. + public static let verifyCredentials = Strings.tr("Localizable", "verifyCredentials", fallback: "Approve credentials") + /// Text shown on the confirm email view to remind the user what to do + public static let verifyYourEmailAddressDescription = Strings.tr("Localizable", "verifyYourEmailAddress_description", fallback: "Please enter your password to verify your email address") + /// Text shown when the creation of a version as a new file was successful + public static let versionCreatedAsANewFileSuccessfully = Strings.tr("Localizable", "Version created as a new file successfully.", fallback: "Version was created as a new file") + /// Title of section to display number of all historical versions of files. + public static let versions = Strings.tr("Localizable", "versions", fallback: "Versions") + /// Label displayed during checking the strength of the password introduced. Represents Very Weak security + public static let veryWeak = Strings.tr("Localizable", "veryWeak", fallback: "Very weak") + /// Title of the button in the contact info screen to start a video call + public static let video = Strings.tr("Localizable", "Video", fallback: "Video") + /// Label used near to the option selected to encode the videos uploaded to a chat (Low, Medium, Original) + public static let videoQuality = Strings.tr("Localizable", "videoQuality", fallback: "Video quality") + /// Title for video explorer view + public static let videos = Strings.tr("Localizable", "Videos", fallback: "Videos") + /// Footer for video uploads switch section when enabled + public static let videosWillBeUploadedToTheCameraUploadsFolder = Strings.tr("Localizable", "Videos will be uploaded to the Camera Uploads folder.", fallback: "Videos will be uploaded to the Camera uploads folder.") + /// Button title which, if tapped, will trigger the action of opening a folder containg this file + public static let viewInFolder = Strings.tr("Localizable", "View in Folder", fallback: "View in folder") + /// Section title of the 'Sorting And View Mode' view inside of Settings - Appearence - Sorting And View Mode. + public static let viewModePreference = Strings.tr("Localizable", "View mode preference", fallback: "View mode preference") + /// Text indicating to the user that can perform an action to view more results + public static let viewMore = Strings.tr("Localizable", "VIEW MORE", fallback: "View more") + /// Link to the public code of the app + public static let viewSourceCode = Strings.tr("Localizable", "View Source Code", fallback: "View source code") + /// Title show on the hall of My Account section that describes a place where you can view, edit and upgrade your account and profile + public static let viewAndEditProfile = Strings.tr("Localizable", "viewAndEditProfile", fallback: "View and edit profile") + /// Menu option from the `Add` section that allows the user to make voice call. + public static let voice = Strings.tr("Localizable", "Voice", fallback: "Voice") + /// Menu option from the `Add` section that allows the user to share voice clip + public static let voiceClip = Strings.tr("Localizable", "Voice Clip", fallback: "Voice clip") + /// Text shown when a notification or the last message of a chat corresponds to a voice clip + public static let voiceMessage = Strings.tr("Localizable", "Voice message", fallback: "Voice message") + /// Section title of a button where you can enable mobile data for voice and video calls. + public static let voiceAndVideoCalls = Strings.tr("Localizable", "voiceAndVideoCalls", fallback: "Voice and video calls") + /// + public static let warning = Strings.tr("Localizable", "warning", fallback: "Warning") + /// A log message in a chat conversation to tell the reader that a participant [A] was removed from the group chat by the moderator [B]. Please keep [A] and [B], they will be replaced by the participant and the moderator names at runtime. For example: Alice was removed from the group chat by Frank. + public static let wasRemovedFromTheGroupChatBy = Strings.tr("Localizable", "wasRemovedFromTheGroupChatBy", fallback: "[A] was removed from the group chat by [B].") + /// Footer for HEIC format section + public static let weRecommendJPGAsItsTheMostCompatibleFormatForPhotos = Strings.tr("Localizable", "We recommend JPG, as its the most compatible format for photos.", fallback: "We recommend JPG, as it is the most compatible format for photos.") + /// Detailed explanation of why the user should give permission to deliver notifications + public static let weWouldLikeToSendYouNotificationsSoYouReceiveNewMessagesOnYourDeviceInstantly = Strings.tr("Localizable", "We would like to send you notifications so you receive new messages on your device instantly.", fallback: "We would like to send you notifications so you receive new messages on your device instantly.") + /// + public static let `weak` = Strings.tr("Localizable", "weak", fallback: "Weak") + /// Description for the ‘Two-Factor Authentication’ in the security settings section. + public static let whatIsTwoFactorAuthentication = Strings.tr("Localizable", "whatIsTwoFactorAuthentication", fallback: "Two-factor authentication is a second layer of security for your account.") + /// Footer text for Camera Upload switch section when camera upload is disabled + public static let whenEnabledPhotosWillBeUploaded = Strings.tr("Localizable", "When enabled, photos will be uploaded.", fallback: "When enabled, photos will be uploaded.") + /// Footer for video uploads switch section when disabled + public static let whenEnabledVideosWillBeUploaded = Strings.tr("Localizable", "When enabled, videos will be uploaded.", fallback: "When enabled, videos will be uploaded.") + /// Warning message to alert user about logout in My Account section if has offline files and transfers in progress. + public static let whenYouLogoutFilesFromYourOfflineSectionWillBeDeletedFromYourDeviceAndOngoingTransfersWillBeCancelled = Strings.tr("Localizable", "When you logout, files from your Offline section will be deleted from your device and ongoing transfers will be cancelled.", fallback: "When you log out, files from your Offline section will be deleted from your device and ongoing transfers will be cancelled.") + /// Warning message to alert user about logout in My Account section if has offline files. + public static let whenYouLogoutFilesFromYourOfflineSectionWillBeDeletedFromYourDevice = Strings.tr("Localizable", "When you logout, files from your Offline section will be deleted from your device.", fallback: "When you log out, files from your Offline section will be deleted from your device.") + /// Warning message to alert user about logout in My Account section if has transfers in progress. + public static let whenYouLogoutOngoingTransfersWillBeCancelled = Strings.tr("Localizable", "When you logout, ongoing transfers will be cancelled.", fallback: "When you log out, ongoing transfers will be cancelled.") + /// Message shown when users with a business account (no administrators of a business account) try to enable the Camera Uploads, to advise them that the administrator do have the ability to view their data. + public static let whileMEGADoesNotHaveAccessToYourDataYourOrganizationAdministratorsDoHaveTheAbilityToControlAndViewTheCameraUploadsInYourUserAccount = Strings.tr("Localizable", "While MEGA does not have access to your data, your organization administrators do have the ability to control and view the Camera Uploads in your user account", fallback: "MEGA cannot access your data. However, your Business account administrator can access your Camera uploads.") + /// Text for button to open an helping view + public static let whyAmISeeingThis = Strings.tr("Localizable", "Why am I seeing this?", fallback: "Why am I seeing this?") + /// Question button to present a view where it's explained what is the Recovery Key + public static let whyDoINeedARecoveryKey = Strings.tr("Localizable", "whyDoINeedARecoveryKey", fallback: "Why do I need a Recovery key?") + /// Title of the dialog displayed to start setup the Two-Factor Authentication + public static let whyYouDoNeedTwoFactorAuthentication = Strings.tr("Localizable", "whyYouDoNeedTwoFactorAuthentication", fallback: "Why do you need two-factor authentication?") + /// Description text of the dialog displayed to start setup the Two-Factor Authentication + public static let whyYouDoNeedTwoFactorAuthenticationDescription = Strings.tr("Localizable", "whyYouDoNeedTwoFactorAuthenticationDescription", fallback: "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.") + /// Label to show that an error related with an write error occurs during a SDK operation. + public static let writeError = Strings.tr("Localizable", "Write error", fallback: "Write error") + /// 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 + public static func writeAMessage(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "writeAMessage", p1, fallback: "Write a message to %s…") + } + /// Error message shown when the purchase has failed + public static func wrongPurchase(_ p1: Any, _ p2: Int) -> String { + return Strings.tr("Localizable", "wrongPurchase", String(describing: p1), p2, fallback: "Wrong purchase %@ (%ld)") + } + /// [X] will be replaced by a plural number, indicating the total number of contacts the user has + public static let xContactsSelected = Strings.tr("Localizable", "XContactsSelected", fallback: "[X] contacts") + /// success message when sending multiple files. Please do not modify the %d placeholder. + public static func xfilesSentSuccesfully(_ p1: Int) -> String { + return Strings.tr("Localizable", "xfilesSentSuccesfully", p1, fallback: "%d files sent") + } + /// String shown when multi selection is enabled and the user has more than one item selected. + public static func xSelected(_ p1: Int) -> String { + return Strings.tr("Localizable", "xSelected", p1, fallback: "%d selected") + } + /// Message to display the number of historical versions of files. Please keep [X] as it will be replaced at the runtime. + public static let xVersions = Strings.tr("Localizable", "xVersions", fallback: "[X] versions") + /// "Yearly" subscriptions + public static let yearly = Strings.tr("Localizable", "yearly", fallback: "Yearly") + /// A user can mark a folder or file with its own colour, in this case “Yellow”. + public static let yellow = Strings.tr("Localizable", "Yellow", fallback: "Yellow") + /// + public static let yes = Strings.tr("Localizable", "yes", fallback: "Yes") + /// + public static let yesterday = Strings.tr("Localizable", "Yesterday", fallback: "Yesterday") + /// Response text after clicking Accept on an incoming contact request notification. + public static let youAcceptedAContactRequest = Strings.tr("Localizable", "You accepted a contact request", fallback: "You accepted a contact request") + /// Title shown when the user reconnect in a call. + public static let youAreBack = Strings.tr("Localizable", "You are back!", fallback: "You are back.") + /// Extra information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings. + public static let youCanTurnOnMobileDataForThisAppInSettings = Strings.tr("Localizable", "You can turn on mobile data for this app in Settings.", fallback: "You can turn on mobile data for this app in the device’s Settings.") + /// Error shown when a Business account user (sub-user or admin) tries to remove a contact which is part of the same Business account. Please, keep the placeholder, it will be replaced with the name or email of the account, for example: Jane Appleseed or ja@mega.nz + public static func youCannotRemove1SAsAContactBecauseTheyArePartOfYourBusinessAccount(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "You cannot remove %1$s as a contact because they are part of your Business account.", p1, fallback: "You cannot remove %1$s as a contact because they are part of your Business account.") + } + /// Response text after clicking Deny on an incoming contact request notification. + public static let youDeniedAContactRequest = Strings.tr("Localizable", "You denied a contact request", fallback: "You denied a contact request") + /// Text message to remind user to resend verification code + public static let youDidnTReceiveACode = Strings.tr("Localizable", "You didn't receive a code?", fallback: "You didn’t receive a code?") + /// Text shown in a notification to let the user know that has joined a public chat room after login or account creation + public static func youHaveJoined(_ p1: Any) -> String { + return Strings.tr("Localizable", "You have joined %@", String(describing: p1), fallback: "You have joined %@") + } + /// Error message that will show to user when user reached the sms verification daily limit + public static let youHaveReachedTheDailyLimit = Strings.tr("Localizable", "You have reached the daily limit", fallback: "You have reached the daily limit") + /// Shows the error when the limit of reactions per user is reached and the user tries to add one more. Keep the placeholder because is to show limit number in runtime. + public static func youHaveReachedTheMaximumLimitOfDReactions(_ p1: Int) -> String { + return Strings.tr("Localizable", "You have reached the maximum limit of %d reactions.", p1, fallback: "You have reached the maximum limit of %d reactions.") + } + /// Title shown in a page of the on boarding screens explaining that the user keeps the encryption keys + public static let youHoldTheKeys = Strings.tr("Localizable", "You hold the keys", fallback: "You hold the keys") + /// Response text after clicking Ignore on an incoming contact request notification. + public static let youIgnoredAContactRequest = Strings.tr("Localizable", "You ignored a contact request", fallback: "You ignored a contact request") + /// Content of the notification when there is unknown activity on the Chat + public static let youMayHaveNewMessages = Strings.tr("Localizable", "You may have new messages", fallback: "You have new messages") + /// Success message when changing profile information. + public static let youHaveSuccessfullyChangedYourProfile = Strings.tr("Localizable", "youHaveSuccessfullyChangedYourProfile", fallback: "You have changed your profile") + /// Alert text shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device + public static let youNeedATwoFactorAuthenticationApp = Strings.tr("Localizable", "youNeedATwoFactorAuthenticationApp", fallback: "We’re sorry, two-factor authentication cannot be enabled on your device. Please open the App Store to install an authenticator app.") + /// Alert title shown when you are seeing the details of a file and you are not able to access it anymore because it has been removed or moved from the shared folder where it used to be + public static let youNoLongerHaveAccessToThisFileAlertTitle = Strings.tr("Localizable", "youNoLongerHaveAccessToThisFile_alertTitle", fallback: "You no longer have access to this file") + /// Alert title shown when you are seeing the details of a folder and you are not able to access it anymore because it has been removed or moved from the shared folder where it used to be + public static let youNoLongerHaveAccessToThisFolderAlertTitle = Strings.tr("Localizable", "youNoLongerHaveAccessToThisFolder_alertTitle", fallback: "You no longer have access to this folder") + /// Text describing account suspended state to the user + public static let yourAccountHasBeenTemporarilySuspendedForYourSafety = Strings.tr("Localizable", "Your account has been temporarily suspended for your safety.", fallback: "Your account has been temporarily locked for your safety.") + /// Error message that will show to user its account is already verified + public static let yourAccountIsAlreadyVerified = Strings.tr("Localizable", "Your account is already verified", fallback: "Your account is already verified") + /// A dialog title shown to users when their business account is expired. + public static let yourBusinessAccountIsExpired = Strings.tr("Localizable", "Your business account is expired", fallback: "Account deactivated") + /// Over Disk Quota title message that tell customer your data at risk. + public static let yourDataIsAtRisk = Strings.tr("Localizable", "Your Data is at Risk!", fallback: "Your data is at risk!") + /// Locked accounts description text by bad use of user password. This text is 2 of 2 paragraph of a description. + public static let yourPasswordLeakedAndIsNowBeingUsedByBadActorsToLogIntoYourAccountsIncludingButNotLimitedToYourMEGAAccount = Strings.tr("Localizable", "Your password leaked and is now being used by bad actors to log into your accounts, including, but not limited to, your MEGA account.", fallback: "Your password leaked and is now being used by bad actors to log in to your accounts, including, but not limited to, your MEGA account.") + /// 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. + public static let yourPaymentForThe1PlanWasReceived = Strings.tr("Localizable", "Your payment for the %1 plan was received.", fallback: "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. + public static let yourPaymentForThe1PlanWasUnsuccessful = Strings.tr("Localizable", "Your payment for the %1 plan was unsuccessful.", fallback: "We didn’t receive your payment for the %1 plan.") + /// Place holder for enter mobile number field + public static let yourPhoneNumber = Strings.tr("Localizable", "Your phone number", fallback: "Your phone number") + /// Information message shown to users when the operation of removing phone number succeed. + public static let yourPhoneNumberHasBeenRemovedSuccessfully = Strings.tr("Localizable", "Your phone number has been removed successfully.", fallback: "Your phone number has been removed") + /// Message shown when verify phone number successfully + public static let yourPhoneNumberHasBeenVerifiedSuccessfully = Strings.tr("Localizable", "Your phone number has been verified successfully", fallback: "Your phone number has been verified") + /// Title shown in a page of the on boarding screens explaining that the user can backup the photos automatically + public static let yourPhotosInTheCloud = Strings.tr("Localizable", "Your Photos in the Cloud", fallback: "Your photos in the Cloud") + /// The professional pricing plan which the user was on expired %1 days ago. The %1 is a placeholder for the number of days and should not be removed. + public static let yourPROMembershipPlanExpired1DaysAgo = Strings.tr("Localizable", "Your PRO membership plan expired %1 days ago", fallback: "Your Pro plan expired %1 days ago") + /// The professional pricing plan which the user was on expired one day ago. + public static let yourPROMembershipPlanExpired1DayAgo = Strings.tr("Localizable", "Your PRO membership plan expired 1 day ago", fallback: "Your Pro plan expired 1 day ago") + /// 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. + public static let yourPROMembershipPlanWillExpireIn1Days = Strings.tr("Localizable", "Your PRO membership plan will expire in %1 days.", fallback: "Your Pro membership plan will expire in %1 days.") + /// The professional pricing plan which the user is currently on will expire in one day. + public static let yourPROMembershipPlanWillExpireIn1Day = Strings.tr("Localizable", "Your PRO membership plan will expire in 1 day.", fallback: "Your Pro membership plan will expire tomorrow.") + /// uploads over storage quota warning dialog title + public static let yourUploadSCannotProceedBecauseYourAccountIsFull = Strings.tr("Localizable", "Your upload(s) cannot proceed because your account is full", fallback: "Your upload cannot proceed because your Cloud storage is full") + /// + public static let yourAccounHasBeenParked = Strings.tr("Localizable", "yourAccounHasBeenParked", fallback: "Your old account has been parked. You can now log in to your new account.") + /// Text of the alert after opening the recovery link to reset pass being logged. + public static let youRecoveryKeyIsGoingTo = Strings.tr("Localizable", "youRecoveryKeyIsGoingTo", fallback: "Your Recovery key is going to be used to reset your password. Please enter your new password.") + /// + public static let yourPasswordHasBeenReset = Strings.tr("Localizable", "yourPasswordHasBeenReset", fallback: "Your password has been reset. Please log in to your account now.") + /// Message that is shown when the user click on 'Cancel your account' to confirm that he's aware that his data will be deleted. + public static let youWillLooseAllData = Strings.tr("Localizable", "youWillLooseAllData", fallback: "You will lose all data associated with this account. Are you sure you want to proceed?") + /// Alert text that explains what means confirming the action 'Leave' + public static let youWillNoLongerHaveAccessToThisConversation = Strings.tr("Localizable", "youWillNoLongerHaveAccessToThisConversation", fallback: "You will no longer have access to this conversation") + public enum BodyWarnYouMustActImmediatelyToSaveYourData { + /// Over Disk Quota of must act immediately to save your data + public static let warnBody = Strings.tr("Localizable", "You must act immediately to save your data.", fallback: "You must act immediately to save your data.") + } + public enum BodyYouHaveWarnWarnLeftToUpgrade { + /// Over Disk Quota warning message to tell user to how long left before upgrading + public static func body(_ p1: Any) -> String { + return Strings.tr("Localizable", "You have %@ left to upgrade.", String(describing: p1), fallback: "You have %@ left to upgrade.") + } + } + public enum ParagraphWeHaveContactedYouByEmailToBBOnBBButYouStillHaveFilesTakingUpBBInYourMEGAAccountWhichRequiresYouToContactSupportForACustomPlan { + /// Over Disk Quota warning message to give customer subscription plan upgrade advice according to cloud space used and subject to deletion deadline and warning dates. + public static func paragraph(_ p1: Any, _ p2: Any, _ p3: Any, _ p4: Any) -> String { + return Strings.tr("Localizable", "We have contacted you by email to %@ on %@ but you still have %@ files taking up %@ in your MEGA account, which requires you to contact support for a custom plan.", String(describing: p1), String(describing: p2), String(describing: p3), String(describing: p4), fallback: "We have contacted you by email to %@ on %@ but you still have %@ files taking up %@ in your MEGA account, which requires you to contact support for a custom plan.") + } + } + public enum ParagraphWeHaveContactedYouByEmailToBBOnBBButYouStillHaveFilesTakingUpBBInYourMEGAAccountWhichRequiresYouToUpgradeToBB { + /// Over Disk Quota warning message to give customer subscription plan upgrade advice according to cloud space used and subject to deletion deadline and warning dates. + public static func paragraph(_ p1: Any, _ p2: Any, _ p3: Any, _ p4: Any, _ p5: Any) -> String { + return Strings.tr("Localizable", "We have contacted you by email to %@ on %@ but you still have %@ files taking up %@ in your MEGA account, which requires you to upgrade to %@.", String(describing: p1), String(describing: p2), String(describing: p3), String(describing: p4), String(describing: p5), fallback: "We have contacted you by email to %@ on %@ but you still have %@ files taking up %@ in your MEGA account, which requires you to upgrade to %@.") + } + } + public enum AddYourPhoneNumberToMEGA { + /// Description to encourage users to add phone number to their accounts when there are not achievements + public static let thisMakesItEasierForYourContactsToFindYouOnMEGA = Strings.tr("Localizable", "Add your phone number to MEGA. This makes it easier for your contacts to find you on MEGA.", fallback: "Add your phone number to MEGA. This makes it easier for your contacts to find you on MEGA.") + } + public enum AllCurrentFilesWillRemain { + /// A warning note about deleting all file versions in the settings section. + public static let onlyHistoricVersionsOfYourFilesWillBeDeleted = Strings.tr("Localizable", "All current files will remain. Only historic versions of your files will be deleted.", fallback: "All current files will remain. Only historic versions of your files will be deleted.") + } + public enum AnErrorHasOccurred { + /// Message show when the history of a chat hasn’t been successfully deleted + public static let theChatHistoryHasNotBeenSuccessfullyCleared = Strings.tr("Localizable", "An error has occurred. The chat history has not been successfully cleared", fallback: "An error has occurred. The chat history hasn’t been cleared") + } + public enum CameraUploadsIsAnEssentialFeatureForAnyMobileDeviceAndWeHaveGotYouCovered { + /// Description shown in a page of the onboarding screens explaining the camera uploads feature + public static let createYourAccountNow = Strings.tr("Localizable", "Camera Uploads is an essential feature for any mobile device and we have got you covered. Create your account now.", fallback: "Camera uploads is an essential feature for any mobile device and we have got you covered. Create your account now.") + } + public enum CompressionQualityWhenToTranscodeHEVCVideosToH { + /// Footer for video compression quality + public static let _264Format = Strings.tr("Localizable", "Compression quality when to transcode HEVC videos to H.264 format.", fallback: "Compression quality when HEVC videos are transcoded to H.264 format.") + } + public enum CouldnTLoad { + /// Explanation when loading voice message error + public static let redTapToRetryRED = Strings.tr("Localizable", "Couldn't load. [RED]Tap to retry[/RED]", fallback: "Couldn’t load. [RED]Tap to retry[/RED]") + } + public enum DeleteAllMessagesAndFilesSharedInThisConversationFromBothParties { + /// Text show under the setting 'Clear Chat History' to explain what will happen if used + public static let thisActionIsIrreversible = Strings.tr("Localizable", "Delete all messages and files shared in this conversation from both parties. This action is irreversible", fallback: "Delete all messages and files shared in this conversation. This action is irreversible.") + } + public enum EmailAlreadySent { + /// Error text shown when requesting email for email verification within 10 minutes + public static let pleaseWaitAFewMinutesBeforeTryingAgain = Strings.tr("Localizable", "Email already sent. Please wait a few minutes before trying again.", fallback: "Email already sent. Please wait a few minutes before trying again.") + } + public enum EnableOrDisableFileVersioningForYourEntireAccount { + /// Subtitle of the option to enable or disable file versioning on Settings section + public static let brYouMayStillReceiveFileVersionsFromSharedFoldersIfYourContactsHaveThisEnabled = Strings.tr("Localizable", "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.", fallback: "Enable or disable file versioning for your entire account.\nDisabling file versioning does not prevent your contacts from creating new versions in shared folders.") + } + public enum Error { + /// Message show when a call cannot be established because there are too many participants in the group call + public static let noMoreParticipantsAreAllowedInThisGroupCall = Strings.tr("Localizable", "Error. No more participants are allowed in this group call.", fallback: "You are not allowed to join this call as it has reached the maximum number of participants.") + /// Message show when a user cannot activate the video in a group call because the max number of videos has been reached + public static let noMoreVideoAreAllowedInThisGroupCall = Strings.tr("Localizable", "Error. No more video are allowed in this group call.", fallback: "You are not allowed to enable video as this call has reached the maximum number of participants using video.") + } + public enum FreeUpSomeSpaceByDeletingAppsYouNoLongerUseOrLargeVideoFilesInYourGallery { + /// Dialog description for the no enough device storage screen + public static func youCanManageYourStorageIn(_ p1: Any) -> String { + return Strings.tr("Localizable", "Free up some space by deleting apps you no longer use or large video files in your gallery. You can manage your storage in %@", String(describing: p1), fallback: "Free up some space by deleting apps you no longer use or large video files in your gallery. You can manage your storage in %@") + } + } + public enum GetFreeWhenYouAddYourPhoneNumber { + /// Free storage description to encourage users to add phone number to their accounts + public static func thisMakesItEasierForYourContactsToFindYouOnMEGA(_ p1: Any) -> String { + return Strings.tr("Localizable", "Get free %@ when you add your phone number. This makes it easier for your contacts to find you on MEGA.", String(describing: p1), fallback: "Get %@ free when you add your phone number. This makes it easier for your contacts to find you on MEGA.") + } + } + public enum MEGANeedsAMinimumOf { + public enum FreeUpSomeSpaceByDeletingAppsYouNoLongerUseOrLargeVideoFilesInYourGallery { + /// Message shown when you try to downlonad files bigger than the available memory. + public static func youCanAlsoManageWhatMEGAStoresOnYourDevice(_ p1: Any) -> String { + return Strings.tr("Localizable", "MEGA needs a minimum of %@. Free up some space by deleting apps you no longer use or large video files in your gallery. You can also manage what MEGA stores on your device.", String(describing: p1), fallback: "MEGA needs a minimum of %@. Free up some space by deleting apps you no longer use or large video files in your gallery. You can also manage what MEGA stores on your device.") + } + } + } + public enum NowYouCanChooseToConvertTheHEIFHEVCPhotosAndVideosToTheMostCompatibleJPEGH { + /// A messge for camera upload v2 migration + public static let _264Formats = Strings.tr("Localizable", "Now you can choose to convert the HEIF/HEVC photos and videos to the most compatible JPEG/H.264 formats.", fallback: "Now you can choose to convert HEIC/HEVC photos and videos to the more widely compatible JPEG/H.264 formats.") + } + public enum OurEndToEndEncryptionSystemRequiresAUniqueKeyAutomaticallyGeneratedForThisFile { + /// Export links dialog -> Keys tip about links and keys. + public static let aLinkWithThisKeyIsCreatedByDefaultButYouCanExportTheDecryptionKeySeparatelyForAnAddedLayerOfSecurity = Strings.tr("Localizable", "Our end-to-end encryption system requires a unique key automatically generated for this file. A link with this key is created by default, but you can export the decryption key separately for an added layer of security.", fallback: "Our zero-knowledge encryption system requires a unique key automatically generated for this file or folder. A link with this key is created by default, but you can export the decryption key separately for an added layer of security.") + } + public enum PleaseGoToThePrivacySectionInYourDeviceSSetting { + /// Hint shown to the users, when they want to use the Location Services but they are disabled or restricted for MEGA + public static let enableLocationServicesAndSetMEGAToWhileUsingTheAppOrAlways = Strings.tr("Localizable", "Please go to the Privacy section in your device’s Setting. Enable Location Services and set MEGA to While Using the App or Always.", fallback: "Please go to the Privacy section in your device’s Settings. Enable Location Services and set MEGA to While Using the App or Always.") + } + public enum TheMEGAAppMayNotWorkAsExpectedWithoutTheRequiredPermissions { + /// Message warning the user about the risk of not setting up permissions + public static let areYouSure = Strings.tr("Localizable", "The MEGA app may not work as expected without the required permissions. Are you sure?", fallback: "The MEGA App may not work as expected without the required permissions. Are you sure?") + } + public enum TheRubbishBinCanBeCleanedForYouAutomatically { + /// New server-side rubbish-bin cleaning scheduler description (for PRO users) + public static let theMinimumPeriodIs7Days = Strings.tr("Localizable", "The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days.", fallback: "The Rubbish bin can be emptied for you automatically. The minimum period is 7 days.") + } + public enum TheRubbishBinIsCleanedForYouAutomatically { + /// New server-side rubbish-bin cleaning scheduler description (for Free users) + public static let theMinimumPeriodIs7DaysAndYourMaximumPeriodIs30Days = Strings.tr("Localizable", "The Rubbish Bin is cleaned for you automatically. The minimum period is 7 days and your maximum period is 30 days.", fallback: "The Rubbish bin is emptied for you automatically. The minimum period is 7 days and the maximum period is 30 days.") + } + public enum ThereHasBeenAProblemProcessingYourPayment { + /// Details shown when a Business account is expired. Details for the administrator of the Business account + public static let megaIsLimitedToViewOnlyUntilThisIssueHasBeenFixedInADesktopWebBrowser = Strings.tr("Localizable", "There has been a problem processing your payment. MEGA is limited to view only until this issue has been fixed in a desktop web browser.", fallback: "Your Business account has been deactivated due to payment failure. You won’t be able to access the data stored in your account. To make a payment and reactivate your subscription, log in to MEGA through a browser.") + } + public enum ThereHasBeenAProblemWithYourLastPayment { + /// When logging in during the grace period, the administrator of the Business account will be notified that their payment is overdue, indicating that they need to access MEGA using a desktop browser for more information + public static let pleaseAccessMEGAUsingADesktopBrowserForMoreInformation = Strings.tr("Localizable", "There has been a problem with your last payment. Please access MEGA using a desktop browser for more information.", fallback: "There has been a problem with your last payment. Please access MEGA using a desktop browser for more information.") + } + public enum ThereIsAnActiveCall { + /// Message shown in a chat room when there is an active call + public static let tapToJoin = Strings.tr("Localizable", "There is an active call. Tap to join.", fallback: "There is an active call. Tap to join.") + } + public enum ThereIsAnActiveGroupCall { + /// Message shown in a chat room when there is an active group call + public static let tapToJoin = Strings.tr("Localizable", "There is an active group call. Tap to join.", fallback: "There is an active group call. Tap to join.") + } + public enum ThisLinkIsNotRelatedToThisAccount { + /// Error message shown when opening a link with an account that not corresponds to the link + public static let pleaseLogInWithTheCorrectAccount = Strings.tr("Localizable", "This link is not related to this account. Please log in with the correct account.", fallback: "This link is not related to this account. Please log in with the correct account.") + } + public enum ThisLinkWasSharedWithoutADecryptionKey { + /// Message shown when a link is shared with separated key + public static let doYouWantToShareItsKey = Strings.tr("Localizable", "This link was shared without a decryption key. Do you want to share its key?", fallback: "This link was shared without a decryption key. Do you want to share its key?") + } + public enum ThisWillRemoveYourAssociatedPhoneNumberFromYourAccount { + /// Message for action to remove the registered phone number. + public static let ifYouLaterChooseToAddAPhoneNumberYouWillBeRequiredToVerifyIt = Strings.tr("Localizable", "This will remove your associated phone number from your account. If you later choose to add a phone number you will be required to verify it.", fallback: "This will remove your associated phone number from your account. If you later choose to add a phone number you will be required to verify it.") + } + public enum WeRecommendH { + /// Footer for HEVC format section + public static let _264AsItsTheMostCompatibleFormatForVideos = Strings.tr("Localizable", "We recommend H.264, as its the most compatible format for videos.", fallback: "We recommend H.264, as it is the most compatible format for videos.") + } + public enum WhenFileVersioningIsDisabledTheCurrentVersionWillBeReplacedWithTheNewVersionOnceAFileIsUpdatedAndYourChangesToTheFileWillNoLongerBeRecorded { + /// A confirmation message when the user chooses to disable file versioning. + public static let areYouSureYouWantToDisableFileVersioning = Strings.tr("Localizable", "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?", fallback: "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?") + } + public enum WrongCode { + /// Error message that will show to user when user entered invalid verification code + public static let pleaseTryAgainOrResend = Strings.tr("Localizable", "Wrong code. Please try again or resend.", fallback: "Wrong code. Please try again or resend.") + } + public enum YouAreAboutToDeleteTheVersionHistoriesOfAllFiles { + public enum AnyFileVersionSharedToYouFromAContactWillNeedToBeDeletedByThem { + /// Text of the dialog to delete all the file versions of the account + public static let brBrPleaseNoteThatTheCurrentFilesWillNotBeDeleted = Strings.tr("Localizable", "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.", fallback: "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.") + } + } + public enum YouCanNowSelectWhichSectionTheAppOpensAtLaunch { + /// Dialog description for the change launch tab screen + public static let chooseTheOneThatBetterSuitsYourNeedsWhetherItSChatCloudDriveOrHome = Strings.tr("Localizable", "You can now select which section the app opens at launch. Choose the one that better suits your needs, whether it’s Chat, Cloud Drive, or Home.", fallback: "You can now select which section the app opens at launch. Choose the one that better suits your needs, whether it’s Chat, Cloud drive, or Home.") + } + public enum YouDoNotHaveEnoughStorageToUploadCamera { + public enum FreeUpSpaceByDeletingUnneededAppsVideosOrMusic { + /// Detail for local storage full situation + public static func youCanManageYourStorageIn(_ p1: Any) -> String { + return Strings.tr("Localizable", "You do not have enough storage to upload camera. Free up space by deleting unneeded apps, videos or music. You can manage your storage in %@", String(describing: p1), fallback: "You do not have enough storage for further camera uploads. Free up space by deleting unneeded apps, videos or music. You can manage your storage in %@") + } + } + } + public enum YouDoNotHaveThePermissionsRequiredToRevertThisFile { + public enum InOrderToContinueWeCanCreateANewFileWithTheRevertedData { + /// Confirmation dialog shown to user when they try to revert a node in an incoming ReadWrite share. + public static let wouldYouLikeToProceed = Strings.tr("Localizable", "You do not have the permissions required to revert this file. In order to continue, we can create a new file with the reverted data. Would you like to proceed?", fallback: "You do not have the permissions required to revert this file. In order to continue, we can create a new file with the reverted data. Would you like to proceed?") + } + } + public enum YouNeedAnAuthenticatorAppToEnable2FAOnMEGA { + /// 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 + public static let youCanDownloadAndInstallTheGoogleAuthenticatorDuoMobileAuthyOrMicrosoftAuthenticatorAppForYourPhoneOrTablet = Strings.tr("Localizable", "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.", fallback: "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.") + } + public enum YourAccountHasBeenDisabledByYourAdministrator { + /// Error message appears to sub-users of a business account when they try to login and they are disabled. + public static let pleaseContactYourBusinessAccountAdministratorForFurtherDetails = Strings.tr("Localizable", "Your account has been disabled by your administrator. Please contact your business account administrator for further details.", fallback: "Your account has been deactivated by your administrator. Please contact your Business account administrator for further details.") + } + public enum YourAccountHasBeenRemovedByYourAdministrator { + /// An error message which appears to sub-users of a business account when they try to login and they are deleted. + public static let pleaseContactYourBusinessAccountAdministratorForFurtherDetails = Strings.tr("Localizable", "Your account has been removed by your administrator. Please contact your business account administrator for further details.", fallback: "Your account has been removed by your administrator. Please contact your Business account administrator for further details.") + } + public enum YourAccountHasBeenSuspendedTemporarilyDueToPotentialAbuse { + /// Description to unblock account by verifying phone number + public static let pleaseVerifyYourPhoneNumberToUnlockYourAccount = Strings.tr("Localizable", "Your account has been suspended temporarily due to potential abuse. Please verify your phone number to unlock your account.", fallback: "Your account has been locked temporarily due to potential abuse. Please verify your phone number to unlock your account.") + } + public enum YourAccountIsCurrentlyBSuspendedB { + /// A dialog message which is shown to sub-users of expired business accounts. + public static let youCanOnlyBrowseYourData = Strings.tr("Localizable", "Your account is currently [B]suspended[/B]. You can only browse your data.", fallback: "Your account is currently [B]suspended[/B]. You can only browse your data.") + } + public enum YourConfirmationLinkIsNoLongerValid { + /// + public static let yourAccountMayAlreadyBeActivatedOrYouMayHaveCancelledYourRegistration = Strings.tr("Localizable", "Your confirmation link is no longer valid. Your account may already be activated or you may have cancelled your registration.", fallback: "Your confirmation link is no longer valid. Your account may already be activated or you may have cancelled your registration.") + } + public enum A1SABChangedTheMessageClearingTimeToBA2SAB { + /// System message displayed to all chat participants when one of them enables retention history + public static func b(_ p1: UnsafePointer, _ p2: UnsafePointer) -> String { + return Strings.tr("Localizable", "[A]%1$s[/A][B] changed the message clearing time to[/B][A] %2$s[/A][B].[/B]", p1, p2, fallback: "[A]%1$s[/A][B] changed the message clearing time to[/B][A] %2$s[/A][B].[/B]") + } + } + public enum A1SABDisabledMessageClearing { + /// System message that is shown to all chat participants upon disabling the Retention history. + public static func b(_ p1: UnsafePointer) -> String { + return Strings.tr("Localizable", "[A]%1$s[/A][B] disabled message clearing.[/B]", p1, fallback: "[A]%1$s[/A][B] disabled message clearing.[/B]") + } + } + public enum AGroupCallEndedAC { + /// When an active goup call is ended, shows the duration + public static let durationC = Strings.tr("Localizable", "[A]Group call ended[/A][C]. Duration: [/C]", fallback: "[A]Group call ended[/A][C]. Duration: [/C]") + } + public enum Account { + /// Text listed that includes the amount of storage that a user gets with a certain package. For example: '2 TB Storage'. + public static func storageQuota(_ p1: Any) -> String { + return Strings.tr("Localizable", "account.storageQuota", String(describing: p1), fallback: "%@ storage") + } + public enum Achievement { + public enum Complete { + public enum ValidBonusExpiry { + public enum Detail { + /// Plural format key: "%#@days@" + public static func subtitle(_ p1: Int) -> String { + return Strings.tr("Localizable", "account.achievement.complete.validBonusExpiry.detail.subtitle", p1, fallback: "Plural format key: \"%#@days@\"") + } + } + } + public enum ValidDays { + /// Plural format key: "%#@days@" + public static func subtitle(_ p1: Int) -> String { + return Strings.tr("Localizable", "account.achievement.complete.validDays.subtitle", p1, fallback: "Plural format key: \"%#@days@\"") + } + } + } + public enum DesktopApp { + /// Cell title shown on the achievements view for install MEGA desktop app achievement + public static let title = Strings.tr("Localizable", "account.achievement.desktopApp.title", fallback: "Install the MEGA Desktop App") + public enum Complete { + public enum Explaination { + /// Explanation label shown on the achievements view for complete install MEGA desktop app achievement, %@ is a placeholder. + public static func label(_ p1: Any) -> String { + return Strings.tr("Localizable", "account.achievement.desktopApp.complete.explaination.label", String(describing: p1), fallback: "You have received %@ storage space for installing the MEGA Desktop App.") + } + } + } + public enum Incomplete { + public enum Explaination { + /// Explanation label shown on the achievements view for incomplete install MEGA desktop app achievement, %@ is a placeholder. + public static func label(_ p1: Any) -> String { + return Strings.tr("Localizable", "account.achievement.desktopApp.incomplete.explaination.label", String(describing: p1), fallback: "When you install the MEGA Desktop App you get %@ of complimentary storage space, valid for 365 days. The MEGA Desktop App is available for Windows, macOS and most Linux distributions.") + } + } + } + } + public enum Incomplete { + /// Subtitle of the incomplete achievement, %@ is a placeholder. + public static func subtitle(_ p1: Any) -> String { + return Strings.tr("Localizable", "account.achievement.incomplete.subtitle", String(describing: p1), fallback: "%@ of storage. Valid for 365 days.") + } + } + public enum MobileApp { + /// Cell title shown on the achievements view for install MEGA mobile app achievement + public static let title = Strings.tr("Localizable", "account.achievement.mobileApp.title", fallback: "Install the MEGA Mobile App") + public enum Complete { + public enum Explaination { + /// Explanation label shown on the achievements view for complete install MEGA mobile app achievement, %@ is a placeholder. + public static func label(_ p1: Any) -> String { + return Strings.tr("Localizable", "account.achievement.mobileApp.complete.explaination.label", String(describing: p1), fallback: "You have received %@ storage space for installing the MEGA mobile app.") + } + } + } + public enum Incomplete { + public enum Explaination { + /// Explanation label shown on the achievements view for incomplete install MEGA mobile app achievement, %@ is a placeholder. + public static func label(_ p1: Any) -> String { + return Strings.tr("Localizable", "account.achievement.mobileApp.incomplete.explaination.label", String(describing: p1), fallback: "When you install the MEGA Mobile App you get %@ of complimentary storage space, valid for 365 days. The MEGA Mobile App is available for iOS and Android. ") + } + } + } + } + public enum PhoneNumber { + public enum Complete { + public enum Explaination { + /// Explanation Label shown on the achievements view for complete add phone number achievement, %@ is a placeholder. + public static func label(_ p1: Any) -> String { + return Strings.tr("Localizable", "account.achievement.phoneNumber.complete.explaination.label", String(describing: p1), fallback: "You have received %@ storage space for adding your phone number.") + } + } + } + public enum Incomplete { + public enum Explaination { + /// Explanation Label shown on the achievements view for incomplete add phone number achievement, %@ is a placeholder. + public static func label(_ p1: Any) -> String { + return Strings.tr("Localizable", "account.achievement.phoneNumber.incomplete.explaination.label", String(describing: p1), fallback: "When you add your phone number you get %@ of complimentary storage space, valid for 365 days.") + } + } + } + } + public enum Referral { + /// Subtitle of the referral achievement introduction, %@ is a placeholder. + public static func subtitle(_ p1: Any) -> String { + return Strings.tr("Localizable", "account.achievement.referral.subtitle", String(describing: p1), fallback: "%@ of storage for each referral. \nValid for 365 days.") + } + /// Cell title shown on the achievements view for referral achievement + public static let title = Strings.tr("Localizable", "account.achievement.referral.title", fallback: "Invite your friends") + } + public enum ReferralBonus { + /// Cell title shown on the achievements view for referral achievement + public static let title = Strings.tr("Localizable", "account.achievement.referralBonus.title", fallback: "Referral bonuses") + } + public enum Registration { + /// Cell title shown on the achievements view for registration achievement + public static let title = Strings.tr("Localizable", "account.achievement.registration.title", fallback: "Registration bonus") + public enum Explanation { + /// Label shown on the achievements view for complete registration achievement, %@ is a placeholder. + public static func label(_ p1: Any) -> String { + return Strings.tr("Localizable", "account.achievement.registration.explanation.label", String(describing: p1), fallback: "You have received %@ storage space as your free registration bonus.") + } + } + } + } + public enum ChangeEmail { + /// Hint text to display user's current email + public static let currentEmail = Strings.tr("Localizable", "account.changeEmail.currentEmail", fallback: "Current email address") + } + public enum ChangePassword { + public enum Error { + /// Account, Change Password view. Error shown when you type your current password. + public static let currentPassword = Strings.tr("Localizable", "account.changePassword.error.currentPassword", fallback: "You have entered your current password.") + } + } + public enum CreateAccount { + /// Message for login option on Create Account screen + public static let alreadyHaveAnAccount = Strings.tr("Localizable", "account.createAccount.alreadyHaveAnAccount", fallback: "Already have an account?") + } + public enum Delete { + public enum Subscription { + /// Dialog message shown before deleting an account with active Google play subscription + public static let googlePlay = Strings.tr("Localizable", "account.delete.subscription.googlePlay", fallback: "You have an active MEGA subscription with Google. You must cancel it separately in Google Play, as MEGA is not able to cancel it for you. Visit our Help Centre for more information.") + /// Dialog message shown before deleting an account with active Huawei AppGallery subscription + public static let huaweiAppGallery = Strings.tr("Localizable", "account.delete.subscription.huaweiAppGallery", fallback: "You have an active MEGA subscription with Huawei. You must cancel it separately at Huawei AppGallery, as MEGA is not able to cancel it for you. Visit our Help Centre for more information.") + /// Dialog message shown before deleting an account with active iTunes subscription + public static let itunes = Strings.tr("Localizable", "account.delete.subscription.itunes", fallback: "You have an active MEGA subscription with Apple. You must cancel it separately at Subscriptions, as MEGA is not able to cancel it for you. Visit our Help Centre for more information.") + /// Dialog title shown before deleting an account with active subscription + public static let title = Strings.tr("Localizable", "account.delete.subscription.title", fallback: "Active subscription") + /// Dialog message shown before deleting an account with active webclient subscription + public static let webClient = Strings.tr("Localizable", "account.delete.subscription.webClient", fallback: "This is the last step to delete your account. Both your account and subscription will be deleted and you will permanently lose all the data stored in the cloud. Please enter your password below.") + public enum GooglePlay { + /// Dialog button title which triggers the action to visit Google play + public static let visit = Strings.tr("Localizable", "account.delete.subscription.googlePlay.visit", fallback: "Visit Google Play") + } + public enum HuaweiAppGallery { + /// Dialog button title which triggers the action to visit Huawei app gallery + public static let visit = Strings.tr("Localizable", "account.delete.subscription.huaweiAppGallery.visit", fallback: "Visit AppGallery") + } + public enum Itunes { + /// Dialog button title which triggers the action to visit AppStore Subscriptions + public static let manage = Strings.tr("Localizable", "account.delete.subscription.itunes.manage", fallback: "Manage subscriptions") + } + } + } + public enum Expired { + public enum ProFlexi { + /// Content message of expired Pro Flexi account view + public static let message = Strings.tr("Localizable", "account.expired.proFlexi.message", fallback: "Your Pro Flexi account has been deactivated due to payment failure or you’ve cancelled your subscription. You won’t be able to access the data stored in your account. To make a payment and reactivate your subscription, log in to MEGA through a browser.") + /// Content title of expired Pro Flexi account view + public static let title = Strings.tr("Localizable", "account.expired.proFlexi.title", fallback: "Account deactivated") + } + } + public enum Login { + /// Message on login screen for Create Account + public static let newToMega = Strings.tr("Localizable", "account.login.newToMega", fallback: "New to MEGA?") + } + public enum Profile { + public enum Avatar { + /// Button that allows the user to upload a photo, e.g. his avatar photo + public static let uploadPhoto = Strings.tr("Localizable", "account.profile.avatar.uploadPhoto", fallback: "Upload photo") + } + } + public enum Storage { + /// Text listed that includes the amount of storage that a free user gets: '20GB+ Storage' + public static let freePlan = Strings.tr("Localizable", "account.storage.freePlan", fallback: "[B]20 GB+[/B] Storage") + public enum StorageUsed { + /// Account Storage title label of used storage size + public static let title = Strings.tr("Localizable", "account.storage.storageUsed.title", fallback: "Storage used") + } + public enum TransferUsed { + /// Account Storage title label of used transfer size + public static let title = Strings.tr("Localizable", "account.storage.transferUsed.title", fallback: "Transfer used") + } + } + public enum Suspension { + public enum Message { + /// Error message when trying to login and the account is blocked due to copyright violation + public static let copyright = Strings.tr("Localizable", "account.suspension.message.copyright", fallback: "Your MEGA account has been suspended due to repeated allegations of copyright infringements. This means you cannot access your account or data within it.\n\nCheck your email for more information on how to file a counter-notice.") + /// Error message when trying to login and the account is blocked due to any type of suspension, but copyright suspension + public static let nonCopyright = Strings.tr("Localizable", "account.suspension.message.nonCopyright", fallback: "Your account was terminated due to a breach of MEGA’s Terms of Service.\n\nYou will not be able to regain access to your stored data or be authorised to register a new MEGA account.") + } + } + public enum TransferQuota { + /// Text listed that explain that a free user gets a limited amount of transfer quota. + public static let freePlan = Strings.tr("Localizable", "account.transferQuota.freePlan", fallback: "[B]Limited[/B] transfer") + /// Text listed that includes the amount of transfer quota a user gets per month with a certain package. For example: '8 TB Transfer'. + public static func perMonth(_ p1: Any) -> String { + return Strings.tr("Localizable", "account.transferQuota.perMonth", String(describing: p1), fallback: "%@ transfer") + } + } + public enum Upgrade { + public enum AlreadyHaveACancellableSubscription { + /// Dialog message shown to allow the user to cancel their current subscription activated from another platform and continue with the purchase. + public static let message = Strings.tr("Localizable", "account.upgrade.alreadyHaveACancellableSubscription.message", fallback: "Do you want to cancel your current subscription and continue with the purchase?") + } + public enum AlreadyHaveASubscription { + /// Dialog message shown when a user tries to purchase a subscriptions while having a subscription activated from another platform. + public static let message = Strings.tr("Localizable", "account.upgrade.alreadyHaveASubscription.message", fallback: "You have previously subscribed to a Pro plan with Google Play or AppGallery. Please manually cancel your subscription with them inside Google Play or the Huawei AppGallery on your device and then retry.") + /// Dialog title shown when a user tries to purchase a subscription while having a subscription activated from another platform. + public static let title = Strings.tr("Localizable", "account.upgrade.alreadyHaveASubscription.title", fallback: "You already have an active subscription") + } + public enum NotAvailableWithCurrentPlan { + /// Error message shown when a user tries to purchase plan that is not available with their current plan + public static let message = Strings.tr("Localizable", "account.upgrade.notAvailableWithCurrentPlan.message", fallback: "Not available with your current plan") + } + } + public enum UpgradeSecurity { + /// Upgrade security alert screen title. For cryptographic security upgrade which will be shown only once per account. + public static let title = Strings.tr("Localizable", "account.upgradeSecurity.title", fallback: "Account security upgrade") + public enum Button { + /// Upgrade security alert screen button title. For cryptographic security upgrade which will be shown only once per account. + public static let title = Strings.tr("Localizable", "account.upgradeSecurity.button.title", fallback: "OK, got it") + } + public enum Message { + /// Plural format key: "%#@folderNames@" + public static func sharedFolderNames(_ p1: Int) -> String { + return Strings.tr("Localizable", "account.upgradeSecurity.message.sharedFolderNames", p1, fallback: "Plural format key: \"%#@folderNames@\"") + } + /// Upgrade security alert screen message. For cryptographic security upgrade which will be shown only once per account. + public static let upgrade = Strings.tr("Localizable", "account.upgradeSecurity.message.upgrade", fallback: "We’re upgrading your account’s security. You should see this message only once. If you’ve seen it before, first make sure it has been for this account and not for another MEGA account you have.\n\nIf you’re sure and it’s the second time you’re seeing this message for this account, stop using this account.") + } + } + } + public enum AlbumLink { + public enum Alert { + public enum Message { + /// Album failed to save to cloud drive alert message + public static func albumFailedToSaveToCloudDrive(_ p1: Any) -> String { + return Strings.tr("Localizable", "albumLink.alert.message.albumFailedToSaveToCloudDrive", String(describing: p1), fallback: "“%@” can’t be saved to Cloud drive. Try again later and if the problem continues, contact the person who shared the link with you.") + } + /// Album saved to cloud drive alert message + public static func albumSavedToCloudDrive(_ p1: Any) -> String { + return Strings.tr("Localizable", "albumLink.alert.message.albumSavedToCloudDrive", String(describing: p1), fallback: "“%@” saved to Cloud drive") + } + /// Plural format key: "%#@count@" + public static func filesSaveToCloudDrive(_ p1: Int) -> String { + return Strings.tr("Localizable", "albumLink.alert.message.filesSaveToCloudDrive", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum RenameAlbum { + /// Rename public album alert message + public static func message(_ p1: Any) -> String { + return Strings.tr("Localizable", "albumLink.alert.renameAlbum.message", String(describing: p1), fallback: "An album named “%@” already exists in your Albums. Enter a new name for the album you’re saving.") + } + /// Rename public album alert title + public static let title = Strings.tr("Localizable", "albumLink.alert.renameAlbum.title", fallback: "Rename album") + } + } + public enum ImportFailed { + public enum StorageQuotaWillExceed { + public enum Alert { + /// Importing albums Failed modal because storage quota would exceed alert detail + public static let detail = Strings.tr("Localizable", "albumLink.importFailed.storageQuotaWillExceed.alert.detail", fallback: "You don’t have enough storage to save this album to your Cloud drive. Upgrade now to get more storage.") + /// Importing albums Failed modal because storage quota would exceed alert title + public static let title = Strings.tr("Localizable", "albumLink.importFailed.storageQuotaWillExceed.alert.title", fallback: "Cannot save album") + } + } + } + public enum InvalidAlbum { + public enum Alert { + /// Alert dismiss button title for invalid album link + public static let dissmissButtonTitle = Strings.tr("Localizable", "albumLink.invalidAlbum.alert.dissmissButtonTitle", fallback: "OK, got it") + /// Alert message for invalid album link + public static let message = Strings.tr("Localizable", "albumLink.invalidAlbum.alert.message", fallback: "The link to the album has been removed or the album has been deleted. Contact the person who shared the link with you.") + /// Alert title for invalid album link + public static let title = Strings.tr("Localizable", "albumLink.invalidAlbum.alert.title", fallback: "Cannot access album") + } + } + } + public enum AutoAway { + /// Footer text to explain the meaning of the functionaly 'Auto-away' of your chat status. + public static let footerDescription = Strings.tr("Localizable", "autoAway.footerDescription", fallback: "Set status as Away after [X] of inactivity.") + } + public enum Backups { + /// Backups title + public static let title = Strings.tr("Localizable", "backups.title", fallback: "Backups") + public enum Empty { + public enum State { + /// No backups and old inbox files in the account description message + public static let description = Strings.tr("Localizable", "backups.empty.state.description", fallback: "This is where your backed up files and folders are stored. Your backed up items are “read-only” to protect them from being accidentally modified in your Cloud drive.\nYou can back up items from your computer to MEGA using our desktop app.") + /// No backups and old inbox files in the account message + public static let message = Strings.tr("Localizable", "backups.empty.state.message", fallback: "No items in backups") + } + } + } + public enum Call { + public enum Duration { + /// Plural format key: "%#@hour@" + public static func hour(_ p1: Int) -> String { + return Strings.tr("Localizable", "call.duration.hour", p1, fallback: "Plural format key: \"%#@hour@\"") + } + /// Plural format key: "%#@minute@" + public static func minute(_ p1: Int) -> String { + return Strings.tr("Localizable", "call.duration.minute", p1, fallback: "Plural format key: \"%#@minute@\"") + } + /// Plural format key: "%#@second@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "call.duration.second", p1, fallback: "Plural format key: \"%#@second@\"") + } + public enum HourAndMinute { + /// Plural format key: "%#@hour@" + public static func hour(_ p1: Int) -> String { + return Strings.tr("Localizable", "call.duration.hourAndMinute.hour", p1, fallback: "Plural format key: \"%#@hour@\"") + } + /// Plural format key: "%#@minute@" + public static func minute(_ p1: Int) -> String { + return Strings.tr("Localizable", "call.duration.hourAndMinute.minute", p1, fallback: "Plural format key: \"%#@minute@\"") + } + } + } + } + public enum CameraUploads { + public enum Albums { + /// Plural format key: "%#@count@" + public static func addedItemTo(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.addedItemTo", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Used as a hud message in album content screen. + public static let albumCoverUpdated = Strings.tr("Localizable", "cameraUploads.albums.albumCoverUpdated", fallback: "Album cover updated") + /// Plural format key: "%#@count@" + public static func delete(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.delete", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func deleteAlbumMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.deleteAlbumMessage", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func deleteAlbumSuccess(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.deleteAlbumSuccess", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func deleteAlbumTitle(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.deleteAlbumTitle", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func removedItemFrom(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.removedItemFrom", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func removeShareLinkAlertConfirmButtonTitle(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.removeShareLinkAlertConfirmButtonTitle", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func removeShareLinkAlertMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.removeShareLinkAlertMessage", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func removeShareLinkAlertTitle(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.removeShareLinkAlertTitle", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func removeShareLinkSuccessMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.removeShareLinkSuccessMessage", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Used as a context menu item on album content screen. + public static let selectAlbumCover = Strings.tr("Localizable", "cameraUploads.albums.selectAlbumCover", fallback: "Select album cover") + /// Albums title within CU tab + public static let title = Strings.tr("Localizable", "cameraUploads.albums.title", fallback: "Albums") + public enum AddItems { + public enum Alert { + public enum LimitReached { + /// Plural format key: "%#@count@" + public static func message(_ p1: Int) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.addItems.alert.limitReached.message", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Alert title shown when adding photos once photo selection limit reached. + public static let title = Strings.tr("Localizable", "cameraUploads.albums.addItems.alert.limitReached.title", fallback: "Cannot select more") + } + } + } + public enum Create { + /// Used in add content album view. + public static func addItemsTo(_ p1: Any) -> String { + return Strings.tr("Localizable", "cameraUploads.albums.create.addItemsTo", String(describing: p1), fallback: "Add items to “%@”") + } + public enum Alert { + /// Used in create album popup system album name validation + public static let albumNameNotAllowed = Strings.tr("Localizable", "cameraUploads.albums.create.alert.albumNameNotAllowed", fallback: "This album name is not allowed") + /// Used in create album popup album name validation + public static let enterDifferentName = Strings.tr("Localizable", "cameraUploads.albums.create.alert.enterDifferentName", fallback: "Enter a different name.") + /// Used in create album popup album name validation + public static let enterNewName = Strings.tr("Localizable", "cameraUploads.albums.create.alert.enterNewName", fallback: "Enter a new name.") + /// Used in create album popup. + public static let placeholder = Strings.tr("Localizable", "cameraUploads.albums.create.alert.placeholder", fallback: "New album") + /// Used in create album popup. + public static let title = Strings.tr("Localizable", "cameraUploads.albums.create.alert.title", fallback: "Enter album name") + /// Used in create album popup user album name validation + public static let userAlbumExists = Strings.tr("Localizable", "cameraUploads.albums.create.alert.userAlbumExists", fallback: "An album with this name already exists.") + } + } + public enum CreateAlbum { + /// Create album title + public static let title = Strings.tr("Localizable", "cameraUploads.albums.createAlbum.title", fallback: "Create album") + } + public enum Empty { + /// Used in empty album view. + public static let title = Strings.tr("Localizable", "cameraUploads.albums.empty.title", fallback: "Empty album") + } + public enum Favourites { + /// Favourites album title + public static let title = Strings.tr("Localizable", "cameraUploads.albums.favourites.title", fallback: "Favourites") + } + public enum Gif { + /// Gif album title + public static let title = Strings.tr("Localizable", "cameraUploads.albums.gif.title", fallback: "GIFs") + } + public enum MyAlbum { + /// My album title + public static let title = Strings.tr("Localizable", "cameraUploads.albums.myAlbum.title", fallback: "My albums") + } + public enum Raw { + /// Raw album title + public static let title = Strings.tr("Localizable", "cameraUploads.albums.raw.title", fallback: "RAW") + } + public enum RemovePhotos { + public enum Alert { + /// Used as a alert title when removing album photos. + public static let title = Strings.tr("Localizable", "cameraUploads.albums.removePhotos.alert.title", fallback: "Remove from album?") + } + } + public enum SharedAlbum { + /// Shared album title + public static let title = Strings.tr("Localizable", "cameraUploads.albums.sharedAlbum.title", fallback: "Shared albums") + } + } + public enum Timeline { + /// Timeline title within CU tab + public static let title = Strings.tr("Localizable", "cameraUploads.timeline.title", fallback: "Timeline") + public enum AllMedia { + public enum Empty { + /// Timeline empty with all media filter enable + public static let title = Strings.tr("Localizable", "cameraUploads.timeline.allMedia.empty.title", fallback: "No media found") + } + } + public enum Filter { + /// Choose type headline within filter UI + public static let chooseType = Strings.tr("Localizable", "cameraUploads.timeline.filter.chooseType", fallback: "Choose type") + /// Filter UI remember preferences option + public static let rememberPreferences = Strings.tr("Localizable", "cameraUploads.timeline.filter.rememberPreferences", fallback: "Remember preferences") + /// Show items from headline within filter UI + public static let showItemsFrom = Strings.tr("Localizable", "cameraUploads.timeline.filter.showItemsFrom", fallback: "Show items from:") + public enum Location { + /// Filter UI media location option + public static let allLocations = Strings.tr("Localizable", "cameraUploads.timeline.filter.location.allLocations", fallback: "All locations") + /// Filter UI media location option + public static let cameraUploads = Strings.tr("Localizable", "cameraUploads.timeline.filter.location.cameraUploads", fallback: "Camera uploads") + /// Filter UI media location option + public static let cloudDrive = Strings.tr("Localizable", "cameraUploads.timeline.filter.location.cloudDrive", fallback: "Cloud drive") + } + public enum MediaType { + /// Filter UI media type option + public static let allMedia = Strings.tr("Localizable", "cameraUploads.timeline.filter.mediaType.allMedia", fallback: "All media") + /// Filter UI media type option + public static let images = Strings.tr("Localizable", "cameraUploads.timeline.filter.mediaType.images", fallback: "Images") + /// Filter UI media type option + public static let videos = Strings.tr("Localizable", "cameraUploads.timeline.filter.mediaType.videos", fallback: "Videos") + } + } + } + public enum VideoUploads { + /// Video Uploads title + public static let title = Strings.tr("Localizable", "cameraUploads.videoUploads.title", fallback: "Video uploads") + } + public enum Warning { + /// Banner message on Camera Uploads that warns user that they have limited access to photos + public static let limitedAccessToPhotoMessage = Strings.tr("Localizable", "cameraUploads.warning.limitedAccessToPhotoMessage", fallback: "⚠ MEGA has limited access to your photo library and cannot backup all of your photos. Tap to change permissions.") + } + } + public enum Chat { + /// Button title for incoming call with `initial`, `joining` and `userNoPresent` status + public static let joinCall = Strings.tr("Localizable", "chat.joinCall", fallback: "Join call") + /// Title for chats section + public static let title = Strings.tr("Localizable", "chat.title", fallback: "Chat") + public enum AddToChatMenu { + /// Menu option from the `Add` section that allows the user to share files from Files app + public static let filesApp = Strings.tr("Localizable", "chat.addToChatMenu.filesApp", fallback: "Files") + /// Menu option from the `Add` section that allows the user to share files from scanning + public static let scan = Strings.tr("Localizable", "chat.addToChatMenu.scan", fallback: "Scan") + } + public enum AutoAway { + /// Plural format key: "%#@hour@" + public static func hour(_ p1: Int) -> String { + return Strings.tr("Localizable", "chat.autoAway.hour", p1, fallback: "Plural format key: \"%#@hour@\"") + } + /// Plural format key: "%#@minute@" + public static func minute(_ p1: Int) -> String { + return Strings.tr("Localizable", "chat.autoAway.minute", p1, fallback: "Plural format key: \"%#@minute@\"") + } + public enum Label { + /// Plural format key: "%#@hour@" + public static func hour(_ p1: Int) -> String { + return Strings.tr("Localizable", "chat.autoAway.label.hour", p1, fallback: "Plural format key: \"%#@hour@\"") + } + /// Plural format key: "%#@minute@" + public static func minute(_ p1: Int) -> String { + return Strings.tr("Localizable", "chat.autoAway.label.minute", p1, fallback: "Plural format key: \"%#@minute@\"") + } + } + } + public enum BackButton { + public enum OneToOne { + /// Back button menu item title visible when navigating back to 1:1 chat . + public static func menu(_ p1: Any) -> String { + return Strings.tr("Localizable", "chat.backButton.oneToOne.menu", String(describing: p1), fallback: "Chat with %@") + } + } + } + public enum Call { + public enum QuickAction { + /// Camera button title to toggle camera on calls. + public static let camera = Strings.tr("Localizable", "chat.call.quickAction.camera", fallback: "Camera") + } + public enum WaitingRoom { + public enum Alert { + public enum Button { + /// Call waiting room alert button to admit access + public static let admit = Strings.tr("Localizable", "chat.call.waitingRoom.alert.button.admit", fallback: "Admit") + /// Call waiting room alert button to allow access to everyone + public static let admitAll = Strings.tr("Localizable", "chat.call.waitingRoom.alert.button.admitAll", fallback: "Admit all") + /// Call waiting room alert button to allow access + public static let cancel = Strings.tr("Localizable", "chat.call.waitingRoom.alert.button.cancel", fallback: "Cancel") + /// Call waiting room alert button to deny access + public static let confirmDeny = Strings.tr("Localizable", "chat.call.waitingRoom.alert.button.confirmDeny", fallback: "Deny entry") + /// Call waiting room alert button to deny access + public static let deny = Strings.tr("Localizable", "chat.call.waitingRoom.alert.button.deny", fallback: "Deny") + /// Call waiting room alert button to open waiting room UI + public static let seeWaitingRoom = Strings.tr("Localizable", "chat.call.waitingRoom.alert.button.seeWaitingRoom", fallback: "See waiting room") + } + public enum Message { + /// Call waiting room alert message text to confirm deny access from host + public static func denyAcces(_ p1: Any) -> String { + return Strings.tr("Localizable", "chat.call.waitingRoom.alert.message.denyAcces", String(describing: p1), fallback: "Deny %@ entry?") + } + /// Call waiting room alert message text to notify host that someone is waiting to join + public static func one(_ p1: Any) -> String { + return Strings.tr("Localizable", "chat.call.waitingRoom.alert.message.one", String(describing: p1), fallback: "%@ is waiting to join the call") + } + /// Call waiting room alert message text to notify host that several participants are waiting to join + public static func several(_ p1: Any) -> String { + return Strings.tr("Localizable", "chat.call.waitingRoom.alert.message.several", String(describing: p1), fallback: "%@ participants are waiting to join the call") + } + } + public enum OutsideCallUI { + public enum Message { + /// Call waiting room alert message text to notify host that someone is waiting to join a call (call UI is not visible) + public static func one(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "chat.call.waitingRoom.alert.outsideCallUI.message.one", String(describing: p1), String(describing: p2), fallback: "%@ is waiting to join “%@”") + } + /// Call waiting room alert message text to notify host that several participants are waiting to join a call (call UI is not visible) + public static func several(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "chat.call.waitingRoom.alert.outsideCallUI.message.several", String(describing: p1), String(describing: p2), fallback: "%@ participants are waiting to join “%@”") + } + } + } + } + } + } + public enum CallInProgress { + /// Message shown in a chat room for a call in progress displaying the duration of the call + public static func tapToReturnToCall(_ p1: Any) -> String { + return Strings.tr("Localizable", "chat.callInProgress.tapToReturnToCall", String(describing: p1), fallback: "Tap to return to call %@") + } + } + public enum Chats { + public enum EmptyState { + /// Description for empty chats tab + public static let description = Strings.tr("Localizable", "chat.chats.emptyState.description", fallback: "Chat securely and privately, with anyone and on any device, knowing that no one can read your chats, not even MEGA.") + /// Ttile for empty chats tab + public static let title = Strings.tr("Localizable", "chat.chats.emptyState.title", fallback: "Start chatting now") + public enum Button { + /// Text button for empty chats tab + public static let title = Strings.tr("Localizable", "chat.chats.emptyState.button.title", fallback: "New chat") + } + } + } + public enum IntroductionHeader { + public enum Privacy { + /// Text description for chat privacy communication + public static let description = Strings.tr("Localizable", "chat.introductionHeader.privacy.description", fallback: "MEGA protects your communications with our zero-knowledge encryption system providing essential safety assurances:") + } + } + public enum Link { + /// Chat: Message shown when the chat link was removed successfully + public static let linkRemoved = Strings.tr("Localizable", "chat.link.linkRemoved", fallback: "Link removed") + } + public enum Listing { + public enum Description { + public enum MeetingCreated { + /// Chat Listing - row description for the created meeting + public static func message(_ p1: Any) -> String { + return Strings.tr("Localizable", "chat.listing.description.meetingCreated.message", String(describing: p1), fallback: "%@: created a meeting") + } + } + } + public enum SectionHeader { + public enum PastMeetings { + /// Chat Meetings tab - Section header title for the chat listing screen + public static let title = Strings.tr("Localizable", "chat.listing.sectionHeader.pastMeetings.title", fallback: "Past meetings") + } + } + } + public enum ManageHistory { + public enum Clearing { + public enum Custom { + public enum Option { + /// Plural format key: "%#@day@" + public static func day(_ p1: Int) -> String { + return Strings.tr("Localizable", "chat.manageHistory.clearing.custom.option.day", p1, fallback: "Plural format key: \"%#@day@\"") + } + /// Plural format key: "%#@hour@" + public static func hour(_ p1: Int) -> String { + return Strings.tr("Localizable", "chat.manageHistory.clearing.custom.option.hour", p1, fallback: "Plural format key: \"%#@hour@\"") + } + /// Plural format key: "%#@month@" + public static func month(_ p1: Int) -> String { + return Strings.tr("Localizable", "chat.manageHistory.clearing.custom.option.month", p1, fallback: "Plural format key: \"%#@month@\"") + } + /// Plural format key: "%#@week@" + public static func week(_ p1: Int) -> String { + return Strings.tr("Localizable", "chat.manageHistory.clearing.custom.option.week", p1, fallback: "Plural format key: \"%#@week@\"") + } + /// Plural format key: "%#@year@" + public static func year(_ p1: Int) -> String { + return Strings.tr("Localizable", "chat.manageHistory.clearing.custom.option.year", p1, fallback: "Plural format key: \"%#@year@\"") + } + } + } + } + public enum Message { + /// Text show under the setting 'History Retention' to explain what is the custom value configured. This value is represented by '%1'. The possible values go from 1 hour, to days, weeks or months, up to 1 year. + public static func deleteMessageOlderThanCustomValue(_ p1: Any) -> String { + return Strings.tr("Localizable", "chat.manageHistory.message.deleteMessageOlderThanCustomValue", String(describing: p1), fallback: "Automatically delete messages older than %@") + } + } + } + public enum Map { + /// Menu option from the `Add` section that allows the user to share Location + public static let location = Strings.tr("Localizable", "chat.map.location", fallback: "Location") + public enum Location { + /// Error message shown when enabling geolocation failed + public static func enableGeolocationFailedError(_ p1: Any) -> String { + return Strings.tr("Localizable", "chat.map.location.enableGeolocationFailedError", String(describing: p1), fallback: "Enable geolocation failed. Error: %@") + } + } + } + public enum Match { + /// Label to desing a file matching + public static let file = Strings.tr("Localizable", "chat.match.file", fallback: "File") + } + public enum Meetings { + public enum EmptyState { + /// Description for empty meetings tab + public static let description = Strings.tr("Localizable", "chat.meetings.emptyState.description", fallback: "Talk securely and privately on audio or video calls with friends and colleagues around the world.") + /// Ttile for empty meetings tab + public static let title = Strings.tr("Localizable", "chat.meetings.emptyState.title", fallback: "Start a meeting") + public enum Button { + /// Text button for empty meetings tab + public static let title = Strings.tr("Localizable", "chat.meetings.emptyState.button.title", fallback: "New meeting") + } + } + } + public enum Message { + /// Chat: This is the placeholder text for text view when keyboard is shown + public static let placeholder = Strings.tr("Localizable", "chat.message.placeholder", fallback: "Message…") + /// Plural format key: "%#@count@" + public static func unreadMessage(_ p1: Int) -> String { + return Strings.tr("Localizable", "chat.message.unreadMessage", p1, fallback: "Plural format key: \"%#@count@\"") + } + public enum ChangedRole { + /// A log message in a chat to display that a participant's permission was changed to host role and by whom. This message begins with the user's name who receive the permission change [A]. [B] will be replaced with the person who did it. Please keep the [A], [B] they will be replaced at runtime. Text between [S] and [/S] will have heavier font weight. For example: Alice Jones was changed to host role by John Smith. + public static let host = Strings.tr("Localizable", "chat.message.changedRole.host", fallback: "[A] was changed to [S]host role[/S] by [B]") + /// A log message in a chat to display that a participant's permission was changed to read-only and by whom. This message begins with the user's name who receive the permission change [A]. [B] will be replaced with the person who did it. Please keep [A] and [B], they will be replaced at runtime. Text between [S] and [/S] will have heavier font weight. For example: Alice Jones was changed to read-only by John Smith. + public static let readOnly = Strings.tr("Localizable", "chat.message.changedRole.readOnly", fallback: "[A] was changed to [S]read-only[/S] by [B]") + /// A log message in a chat to display that a participant's permission was changed to standard role and by whom. This message begins with the user's name who receive the permission change [A]. [B] will be replaced with the person who did it. Please keep [A] and [B] they will be replaced at runtime. Text between [S] and [/S] will have heavier font weight. For example: Alice Jones was changed to standard role by John Smith. + public static let standard = Strings.tr("Localizable", "chat.message.changedRole.standard", fallback: "[A] was changed to [S]standard role[/S] by [B]") + } + } + public enum Photos { + /// Description shown in chat screen if the user didn't allow access to photos + public static let allowPhotoAccessMessage = Strings.tr("Localizable", "chat.photos.allowPhotoAccessMessage", fallback: "To share photos and videos, grant MEGA access to your gallery") + } + public enum Selector { + /// Chat section selector tab for chats + public static let chat = Strings.tr("Localizable", "chat.selector.chat", fallback: "Chats") + /// Chat section selector tab for meetings + public static let meeting = Strings.tr("Localizable", "chat.selector.meeting", fallback: "Meetings") + } + public enum SendLocation { + public enum Map { + /// Type of data displayed in a map view. A satellite image of the area with road and road name information layered on top. + public static let hybrid = Strings.tr("Localizable", "chat.sendLocation.map.hybrid", fallback: "Hybrid") + /// Type of data displayed in a map view. Satellite imagery of the area. + public static let satellite = Strings.tr("Localizable", "chat.sendLocation.map.satellite", fallback: "Satellite") + /// Type of data displayed in a map view. Standard imagery of the area. + public static let standard = Strings.tr("Localizable", "chat.sendLocation.map.standard", fallback: "Standard") + } + } + public enum Status { + public enum Duration { + /// Plural format key: "%#@minute@" + public static func minute(_ p1: Int) -> String { + return Strings.tr("Localizable", "chat.status.duration.minute", p1, fallback: "Plural format key: \"%#@minute@\"") + } + } + } + } + public enum CloudDrive { + public enum Browser { + /// List option shown on the details of a file or folder after the user has copied it + public static let paste = Strings.tr("Localizable", "cloudDrive.browser.paste", fallback: "Paste") + public enum SaveToCloudDrive { + /// Browser save to cloud drive select button title + public static let title = Strings.tr("Localizable", "cloudDrive.browser.saveToCloudDrive.title", fallback: "Save to Cloud drive") + } + } + public enum Info { + public enum Node { + /// Title label of a node property. + public static let location = Strings.tr("Localizable", "cloudDrive.info.node.location", fallback: "Location") + } + } + public enum MediaDiscovery { + /// Exit Text on Media Discovery Navigation Bar + public static let exit = Strings.tr("Localizable", "cloudDrive.mediaDiscovery.exit", fallback: "Exit") + } + public enum Menu { + public enum MediaDiscovery { + /// Menu option from the `Add` section that displays all media files under current folder + public static let title = Strings.tr("Localizable", "cloudDrive.menu.mediaDiscovery.title", fallback: "Media discovery") + } + } + public enum NodeInfo { + /// Show the node owner's name. The %@ is the owner's name + public static func owner(_ p1: Any) -> String { + return Strings.tr("Localizable", "cloudDrive.nodeInfo.owner", String(describing: p1), fallback: "%@ (owner)") + } + } + public enum ScanDocument { + /// Default title given to the document created when you use the option 'Scan Document' in the app + public static func defaultName(_ p1: Any) -> String { + return Strings.tr("Localizable", "cloudDrive.scanDocument.defaultName", String(describing: p1), fallback: "Scan %@") + } + } + public enum Sort { + /// A menu item in the left panel drop down menu to allow sorting by label. + public static let label = Strings.tr("Localizable", "cloudDrive.sort.label", fallback: "Label") + } + public enum Upload { + /// Tapping on this button open Files.app and allow users select files to upload them to MEGA + public static let importFromFiles = Strings.tr("Localizable", "cloudDrive.upload.importFromFiles", fallback: "Import from Files") + } + } + public enum Contact { + public enum Invite { + /// Text to send as SMS message to user contacts inviting them to MEGA + public static let message = Strings.tr("Localizable", "contact.invite.message", fallback: "You have a MEGA Chat request waiting. Register an account on MEGA and get 20 GB free lifetime storage.") + } + } + public enum ContactInfo { + public enum BackButton { + /// Name of the screen of contact info shown when back button is long pressed + public static let menu = Strings.tr("Localizable", "contactInfo.backButton.menu", fallback: "Contact info") + } + } + public enum Device { + public enum Center { + /// Device Center title + public static let title = Strings.tr("Localizable", "device.center.title", fallback: "Device centre") + public enum Backup { + public enum BackupStopped { + public enum Status { + /// Device Center backup backup stopped status + public static let message = Strings.tr("Localizable", "device.center.backup.backupStopped.status.message", fallback: "Backup stopped") + } + } + public enum Blocked { + public enum Status { + /// Device Center backup blocked status + public static let message = Strings.tr("Localizable", "device.center.backup.blocked.status.message", fallback: "Blocked") + } + } + public enum Disabled { + public enum Status { + /// Device Center backup disabled status + public static let message = Strings.tr("Localizable", "device.center.backup.disabled.status.message", fallback: "Disabled") + } + } + public enum Error { + public enum Status { + /// Device Center backup error status + public static let message = Strings.tr("Localizable", "device.center.backup.error.status.message", fallback: "Error") + } + } + public enum Initialising { + public enum Status { + /// Device Center backup initialising status + public static let message = Strings.tr("Localizable", "device.center.backup.initialising.status.message", fallback: "Initialising…") + } + } + public enum NoCameraUploads { + public enum Status { + /// Device Center backup no camera uploads status + public static let message = Strings.tr("Localizable", "device.center.backup.noCameraUploads.status.message", fallback: "No camera uploads") + } + } + public enum Offline { + public enum Status { + /// Device Center backup offline status + public static let message = Strings.tr("Localizable", "device.center.backup.offline.status.message", fallback: "Offline") + } + } + public enum OutOfQuota { + public enum Status { + /// Device Center backup out of quota status + public static let message = Strings.tr("Localizable", "device.center.backup.outOfQuota.status.message", fallback: "Out of quota") + } + } + public enum Paused { + public enum Status { + /// Device Center backup paused status + public static let message = Strings.tr("Localizable", "device.center.backup.paused.status.message", fallback: "Paused") + } + } + public enum Scanning { + public enum Status { + /// Device Center backup scanning status + public static let message = Strings.tr("Localizable", "device.center.backup.scanning.status.message", fallback: "Scanning…") + } + } + public enum UpToDate { + public enum Status { + /// Device Center backup up to date status + public static let message = Strings.tr("Localizable", "device.center.backup.upToDate.status.message", fallback: "Up to date") + } + } + public enum Updating { + public enum Status { + /// Device Center backup updating status + public static let message = Strings.tr("Localizable", "device.center.backup.updating.status.message", fallback: "Updating…") + } + } + } + public enum Current { + public enum Device { + /// Device Center list current device title + public static let title = Strings.tr("Localizable", "device.center.current.device.title", fallback: "This device") + } + } + public enum Default { + public enum Device { + /// Default device name in case the device name cannot be obtained + public static let title = Strings.tr("Localizable", "device.center.default.device.title", fallback: "Unknown device") + } + } + public enum Other { + public enum Devices { + /// Device Center list other devices title + public static let title = Strings.tr("Localizable", "device.center.other.devices.title", fallback: "Other devices") + } + } + public enum Show { + public enum In { + public enum Backups { + public enum Action { + /// Show in Backups section action title + public static let title = Strings.tr("Localizable", "device.center.show.in.backups.action.title", fallback: "Show in Backups") + } + } + public enum Cloud { + public enum Drive { + public enum Action { + /// Show in Cloud Drive section action title + public static let title = Strings.tr("Localizable", "device.center.show.in.cloud.drive.action.title", fallback: "Show in Cloud drive") + } + } + } + } + } + } + } + public enum Dialog { + public enum Add { + public enum Items { + public enum Backup { + public enum Action { + /// Add items to a backup folder action title + public static let title = Strings.tr("Localizable", "dialog.add.items.backup.action.title", fallback: "Add") + } + public enum Folder { + public enum Warning { + /// Add new items to a backup folder warning message + public static let message = Strings.tr("Localizable", "dialog.add.items.backup.folder.warning.message", fallback: "Adding items to this folder changes the backup destination. The backup will be turned off for safety. Is this what you want to do? Backups can be re-enabled with the MEGA Desktop App.") + /// Add new items to a backup folder warning title. %@ is a folder + public static func title(_ p1: Any) -> String { + return Strings.tr("Localizable", "dialog.add.items.backup.folder.warning.title", String(describing: p1), fallback: "Add Item to “%@”") + } + } + } + } + } + } + public enum Backup { + public enum Folder { + public enum Location { + public enum Warning { + /// Change the default backup location warning message + public static let message = Strings.tr("Localizable", "dialog.backup.folder.location.warning.message", fallback: "Moving this folder changes the backup destination. The backup will be turned off for safety. Is this what you want to do? Backups can be re-enabled with the MEGA Desktop App.") + /// Change the default backup location warning title. %@ is a folder + public static func title(_ p1: Any) -> String { + return Strings.tr("Localizable", "dialog.backup.folder.location.warning.title", String(describing: p1), fallback: "Move “%@”") + } + } + } + } + public enum Setup { + /// Backup setup warning message + public static let message = Strings.tr("Localizable", "dialog.backup.setup.message", fallback: "Use our desktop app to ensure your backup folder is synchronised with your MEGA Cloud.") + /// Backup setup warning title + public static let title = Strings.tr("Localizable", "dialog.backup.setup.title", fallback: "Set up Backup") + } + public enum Warning { + public enum Confirm { + /// Backup warning confirmation message. %@ is an action: move backup, delete backup, disable backup + public static func message(_ p1: Any) -> String { + return Strings.tr("Localizable", "dialog.backup.warning.confirm.message", String(describing: p1), fallback: "Please type “%@” to confirm this action") + } + } + } + } + public enum CallAttempt { + /// Detail message shown when you try to call someone that is not you contact in MEGA. The [X] placeholder will be replaced on runtime for the email of the user + public static let contactNotInMEGA = Strings.tr("Localizable", "dialog.callAttempt.contactNotInMEGA", fallback: "Your contact [X] is not on MEGA. In order to call through MEGA’s encrypted chat you need to invite your contact") + } + public enum Confirmation { + public enum Error { + /// Backup action confirmation error message + public static let message = Strings.tr("Localizable", "dialog.confirmation.error.message", fallback: "Text entered does not match") + } + } + public enum Cookies { + /// Cookie dialog button label. + public static let accept = Strings.tr("Localizable", "dialog.cookies.accept", fallback: "Accept Cookies") + /// Cookie dialog text. + public static let description = Strings.tr("Localizable", "dialog.cookies.description", fallback: "We use cookies and similar technologies solely for the purposes of providing you with the services you request from MEGA, or for analytics and gathering performance data. We don’t use cookies for ad tracking or sharing any personal information about you with third parties.") + public enum Title { + /// Cookie dialog title + public static let yourPrivacy = Strings.tr("Localizable", "dialog.cookies.title.yourPrivacy", fallback: "Your privacy") + } + } + public enum Delete { + public enum Backup { + /// Delete a backup folder placeholder for a textfield + public static let placeholder = Strings.tr("Localizable", "dialog.delete.backup.placeholder", fallback: "delete backup") + public enum Action { + /// Delete a backup folder action title + public static let title = Strings.tr("Localizable", "dialog.delete.backup.action.title", fallback: "Move to Rubbish bin") + } + public enum Folder { + public enum Warning { + /// Delete the default backup folder warning message + public static let message = Strings.tr("Localizable", "dialog.delete.backup.folder.warning.message", fallback: "Are you sure you want to delete your backup folder and disable backup you set?") + /// Delete the default backup folder warning title. %@ is a folder + public static func title(_ p1: Any) -> String { + return Strings.tr("Localizable", "dialog.delete.backup.folder.warning.title", String(describing: p1), fallback: "Move “%@” to Rubbish bin") + } + } + } + } + public enum Root { + public enum Backup { + public enum Folder { + public enum Warning { + /// Delete the default backup folder warning message + public static let message = Strings.tr("Localizable", "dialog.delete.root.backup.folder.warning.message", fallback: "You are deleting your backups folder. This will remove all the backups you have set. Are you sure you want to do this?") + } + } + } + } + } + public enum Disable { + public enum Backup { + /// Add items to a backup folder placeholder for a textfield + public static let placeholder = Strings.tr("Localizable", "dialog.disable.backup.placeholder", fallback: "disable backup") + } + } + public enum InviteContact { + /// Detail message shown when a contact has been invited. The [X] placeholder will be replaced on runtime for the email of the invited user + public static let outgoingContactRequest = Strings.tr("Localizable", "dialog.inviteContact.outgoingContactRequest", fallback: "The user [X] has been invited and will appear in your contact list once accepted.") + } + public enum Move { + public enum Backup { + /// Change the location of a backup folder placeholder for a textfield + public static let placeholder = Strings.tr("Localizable", "dialog.move.backup.placeholder", fallback: "move backup") + } + } + public enum Root { + public enum Backup { + public enum Folder { + public enum Location { + public enum Warning { + /// Change the default backup location warning message + public static let message = Strings.tr("Localizable", "dialog.root.backup.folder.location.warning.message", fallback: "You are changing a default backup folder location. This may affect your ability to find your backup folder. Please remember where it is located so that you can find it in the future.") + } + } + } + } + } + public enum Share { + public enum Backup { + public enum Non { + public enum Backup { + public enum Folders { + public enum Warning { + /// Alert the user when trying to share several backup and non-backup folders + public static let message = Strings.tr("Localizable", "dialog.share.backup.non.backup.folders.warning.message", fallback: "Some folders shared are backup folders and read-only. Do you wish to continue?") + } + } + } + } + } + } + public enum ShareOwnerStorageQuota { + /// An error message which is shown when we want to share a file with somebody who has used all the storage space in their MEGA account. + public static let message = Strings.tr("Localizable", "dialog.shareOwnerStorageQuota.message", fallback: "The file cannot be sent as the target user is over their storage quota.") + } + public enum Storage { + public enum AlmostFull { + /// Informs the user that they’ve almost reached the full capacity of their Cloud Drive for a Free account. @% is placeholder. + public static func detail(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "dialog.storage.almostFull.detail", String(describing: p1), String(describing: p2), fallback: "Your Cloud drive is almost full. Upgrade now to a Pro account and get up to %@ TB (%@ GB) of cloud storage space.") + } + } + public enum Odq { + /// Over Disk Quota detail message informing the user that they've reached the full capacity of their accounts. + public static let detail = Strings.tr("Localizable", "dialog.storage.odq.detail", fallback: "Your Cloud storage is full. Please upgrade to a MEGA Pro plan to increase your storage space and enjoy additional benefits. If you remain over your storage limit and do not upgrade or delete some data your account may get locked. Please see the emails we sent you for more information.") + /// Title of Over Disk Quota dialog + public static let title = Strings.tr("Localizable", "dialog.storage.odq.title", fallback: "Please upgrade") + } + public enum Paywall { + public enum Final { + /// Over Disk Quota paywall final detail message. + public static let detail = Strings.tr("Localizable", "dialog.storage.paywall.final.detail", fallback: "Your data will be deleted tomorrow if your account has not been upgraded.") + /// Over Disk Quota paywall final title. + public static let title = Strings.tr("Localizable", "dialog.storage.paywall.final.title", fallback: "Your data is at risk!") + /// Over Disk Quota paywall, final warning label. + public static let warning = Strings.tr("Localizable", "dialog.storage.paywall.final.warning", fallback: "Upgrade today if you wish to keep your data.") + } + public enum NotFinal { + /// Over Disk Quota paywall not final detail message. + public static func detail(_ p1: Any, _ p2: Any, _ p3: Any, _ p4: Any) -> String { + return Strings.tr("Localizable", "dialog.storage.paywall.notFinal.detail", String(describing: p1), String(describing: p2), String(describing: p3), String(describing: p4), fallback: "Unfortunately, you have not taken action in the %@ since our emails were sent to %@, on %@. You are still using %@ storage, which is over your free limit. Please see the emails we have sent you for more information on what you need to do.") + } + /// Over Disk Quota paywall not final title. + public static let title = Strings.tr("Localizable", "dialog.storage.paywall.notFinal.title", fallback: "Account locked") + /// Over Disk Quota paywall, not final warning label. + public static func warning(_ p1: Any) -> String { + return Strings.tr("Localizable", "dialog.storage.paywall.notFinal.warning", String(describing: p1), fallback: "Upgrade within %@ to avoid your data getting deleted.") + } + } + } + } + public enum TurnOnNotifications { + public enum Button { + /// Title of the button to open Settings + public static let primary = Strings.tr("Localizable", "dialog.turnOnNotifications.button.primary", fallback: "Open Settings") + } + public enum Label { + /// The description of Turn on Notifications view + public static let description = Strings.tr("Localizable", "dialog.turnOnNotifications.label.description", fallback: "This way, you will see new messages on your device instantly.") + /// Fourth step to turn on notifications + public static let stepFour = Strings.tr("Localizable", "dialog.turnOnNotifications.label.stepFour", fallback: "4. Turn on Allow notifications") + /// First step to turn on notifications + public static let stepOne = Strings.tr("Localizable", "dialog.turnOnNotifications.label.stepOne", fallback: "1. Open Settings") + /// Third step to turn on notifications + public static let stepThree = Strings.tr("Localizable", "dialog.turnOnNotifications.label.stepThree", fallback: "3. Tap MEGA") + /// Second step to turn on notifications + public static let stepTwo = Strings.tr("Localizable", "dialog.turnOnNotifications.label.stepTwo", fallback: "2. Tap Notifications") + /// The title of Turn on Notifications view + public static let title = Strings.tr("Localizable", "dialog.turnOnNotifications.label.title", fallback: "Turn on notifications") + } + } + } + public enum Dnd { + public enum Duration { + /// Plural format key: "%#@hour@" + public static func hour(_ p1: Int) -> String { + return Strings.tr("Localizable", "dnd.duration.hour", p1, fallback: "Plural format key: \"%#@hour@\"") + } + /// Plural format key: "%#@minute@" + public static func minute(_ p1: Int) -> String { + return Strings.tr("Localizable", "dnd.duration.minute", p1, fallback: "Plural format key: \"%#@minute@\"") + } + } + } + public enum Extensions { + public enum OpenApp { + /// Text shown in extensions to notify users that they have to open the application first + public static let message = Strings.tr("Localizable", "extensions.OpenApp.Message", fallback: "Open the MEGA app and log in to continue.") + } + public enum Share { + public enum Destination { + public enum Section { + /// Plural format key: "%#@file@" + public static func files(_ p1: Int) -> String { + return Strings.tr("Localizable", "extensions.share.destination.section.files", p1, fallback: "Plural format key: \"%#@file@\"") + } + } + } + } + } + public enum FileProviderExtension { + public enum Action { + /// Context menu action seen from native files application when selecting a file that's also present on MEGA + public static let `open` = Strings.tr("Localizable", "fileProviderExtension.action.open", fallback: "Open in MEGA") + } + } + public enum General { + /// Text show to highlight that you can choose a different option + public static let choose = Strings.tr("Localizable", "general.choose", fallback: "Select") + /// Title of one of the Settings sections where you can see the MEGA's 'Cookie Policy' + public static let cookiePolicy = Strings.tr("Localizable", "general.cookiePolicy", fallback: "Cookie Policy") + /// Title of one of the Settings sections where you can see the MEGA's 'Cookie Settings' + public static let cookieSettings = Strings.tr("Localizable", "general.cookieSettings", fallback: "Cookie settings") + /// Text for the action of downloading an item to the offline section + public static let downloadToOffline = Strings.tr("Localizable", "general.downloadToOffline", fallback: "Make available offline") + /// Button title which, if tapped, will trigger the action to export something from MEGA with the objective of sharing it outside of the app + public static let export = Strings.tr("Localizable", "general.export", fallback: "Export") + /// Text for the popup displayed when you try to download file to photos while file is already being downloaded + public static let fileIsBeingDownloaded = Strings.tr("Localizable", "general.fileIsBeingDownloaded", fallback: "File is already being downloaded") + /// Button title which triggers the action to join meeting as Guest + public static let joinMeetingAsGuest = Strings.tr("Localizable", "general.joinMeetingAsGuest", fallback: "Join meeting as guest") + /// Label shown when users lost the internet connection + public static let noIntenerConnection = Strings.tr("Localizable", "general.NoIntenerConnection", fallback: "Unable to connect to the Internet. Please check your connection and try again.") + /// Title for a button to send a file to a chat + public static let sendToChat = Strings.tr("Localizable", "general.sendToChat", fallback: "Send to chat") + /// Button title which, if tapped, will trigger the action of sharing with the contact or contacts selected + public static let share = Strings.tr("Localizable", "general.share", fallback: "Share") + public enum Button { + /// Button title to see the available bonus + public static let getBonus = Strings.tr("Localizable", "general.button.getBonus", fallback: "Get bonus") + } + public enum ChooseLabel { + /// Context menu item which allows to mark folders with own color label + public static let title = Strings.tr("Localizable", "general.chooseLabel.title", fallback: "Label…") + } + public enum Error { + /// Error message shown when trying to rename or create a folder with characters that are not allowed. The %@ will be replaced with the list of invalid characters. For example: The following characters are not allowed: " * / : < > ? \ | + public static func charactersNotAllowed(_ p1: Any) -> String { + return Strings.tr("Localizable", "general.error.charactersNotAllowed", String(describing: p1), fallback: "The following characters are not allowed: %@") + } + } + public enum Filetype { + /// File extensions: .3dm and .3gp. Commercial names should be stay on English + public static let _3DModel = Strings.tr("Localizable", "general.filetype.3DModel", fallback: "3D model") + /// File extension: .3ds. Commercial names should be stay on English + public static let _3ds = Strings.tr("Localizable", "general.filetype.3ds", fallback: "3D scene") + /// File extension: .3g2. Commercial names should be stay on English + public static let _3g2 = Strings.tr("Localizable", "general.filetype.3g2", fallback: "Multimedia") + /// File extension: .7z. Commercial names should be stay on English + public static let _7z = Strings.tr("Localizable", "general.filetype.7z", fallback: "7-Zip compressed") + /// File extension: .ans. Commercial names should be stay on English + public static let ans = Strings.tr("Localizable", "general.filetype.ans", fallback: "ANSI text file") + /// File extension: .apk. Commercial names should be stay on English + public static let apk = Strings.tr("Localizable", "general.filetype.apk", fallback: "Android app") + /// File extension: .app. Commercial names should be stay on English + public static let app = Strings.tr("Localizable", "general.filetype.app", fallback: "macOS app") + /// File extension: .ascii. Commercial names should be stay on English + public static let ascii = Strings.tr("Localizable", "general.filetype.ascii", fallback: "ASCII text") + /// File extension: .asf. Commercial names should be stay on English + public static let asf = Strings.tr("Localizable", "general.filetype.asf", fallback: "Streaming video") + /// File extension: .asx. Is not a commercial name, but a file format name. + public static let asx = Strings.tr("Localizable", "general.filetype.asx", fallback: "Advanced stream") + /// File extension: .aif and .aiff. Is not a commercial name, but a standard + public static let audioInterchange = Strings.tr("Localizable", "general.filetype.audioInterchange", fallback: "Audio interchange") + /// File extension: .avi. Name of a multimedia container format + public static let avi = Strings.tr("Localizable", "general.filetype.avi", fallback: "A/V interleave") + /// File extension: .bat. "DOS" is not a commercial name, but the acronym for disk operating system. "Batch" is common noun + public static let bat = Strings.tr("Localizable", "general.filetype.bat", fallback: "DOS batch") + /// File extension: .bay. Commercial names should be stay on English + public static let bay = Strings.tr("Localizable", "general.filetype.bay", fallback: "Casio RAW image") + /// File extension: .bmp. Commercial names should be stay on English + public static let bmp = Strings.tr("Localizable", "general.filetype.bmp", fallback: "Bitmap image") + /// File extension: .bz2. Commercial names should be stay on English + public static let bz2 = Strings.tr("Localizable", "general.filetype.bz2", fallback: "UNIX compressed") + /// File extension: .c. Commercial names should be stay on English + public static let c = Strings.tr("Localizable", "general.filetype.c", fallback: "C/C++ source code") + /// File extension. Commercial names should be stay on English + public static let cdr = Strings.tr("Localizable", "general.filetype.cdr", fallback: "CorelDRAW image") + /// File extension: .cgi. Is not a commercial name, but the name of an interface specification. "script" is common noun. + public static let cgi = Strings.tr("Localizable", "general.filetype.cgi", fallback: "CGI script") + /// File extension: .class. Commercial names should be stay on English + public static let `class` = Strings.tr("Localizable", "general.filetype.class", fallback: "Java class") + /// File extension: .com. Commercial names should be stay on English + public static let com = Strings.tr("Localizable", "general.filetype.com", fallback: "DOS command") + /// File extension: .tbz and .tgz. Commercial names should be stay on English + public static let compressed = Strings.tr("Localizable", "general.filetype.compressed", fallback: "Compressed") + /// File extensions: .cc, .cpp and .cxx. Commercial names should be stay on English + public static let cpp = Strings.tr("Localizable", "general.filetype.cpp", fallback: "C++ source code") + /// File extension: .css. Commercial names should be stay on English + public static let css = Strings.tr("Localizable", "general.filetype.css", fallback: "CSS style sheet") + /// File extensions: .accdb, .db, .dbf and .pdb. Commercial names should be stay on English + public static let database = Strings.tr("Localizable", "general.filetype.database", fallback: "Database") + /// File extension: .dhtml. Is not a commercial name. "dynamic" is an adjective + public static let dhtml = Strings.tr("Localizable", "general.filetype.dhtml", fallback: "Dynamic HTML") + /// File extension: .dll. Is not a commercial name. "dynamic" is the name of concept + public static let dll = Strings.tr("Localizable", "general.filetype.dll", fallback: "Dynamic link library") + /// File extension: .dxf. Commercial names should be stay on English + public static let dxf = Strings.tr("Localizable", "general.filetype.dxf", fallback: "DXF image") + /// File extension: .eps. Commercial names should be stay on English + public static let eps = Strings.tr("Localizable", "general.filetype.eps", fallback: "EPS image") + /// File extension: .exe. Commercial names should be stay on English + public static let exe = Strings.tr("Localizable", "general.filetype.exe", fallback: "Executable") + /// File extension: .flac. Commercial names should be stay on English + public static let flac = Strings.tr("Localizable", "general.filetype.flac", fallback: "Lossless audio") + /// File extension: .flv. Commercial names should be stay on English + public static let flv = Strings.tr("Localizable", "general.filetype.flv", fallback: "Flash video") + /// File extension: .fnt. Commercial names should be stay on English + public static let fnt = Strings.tr("Localizable", "general.filetype.fnt", fallback: "Windows font") + /// File extension: .fon. Commercial names should be stay on English + public static let fon = Strings.tr("Localizable", "general.filetype.fon", fallback: "Font") + /// File extension: .gadget. Commercial names should be stay on English + public static let gadget = Strings.tr("Localizable", "general.filetype.gadget", fallback: "Windows gadget") + /// File extension: .gif. Commercial names should be stay on English + public static let gif = Strings.tr("Localizable", "general.filetype.gif", fallback: "GIF image") + /// File extension: .gpx. Commercial names should be stay on English + public static let gpx = Strings.tr("Localizable", "general.filetype.gpx", fallback: "GPS exchange") + /// File extension: .gz. Commercial names should be stay on English + public static let gz = Strings.tr("Localizable", "general.filetype.gz", fallback: "GNU compressed") + /// File extensions: .h and .hpp. Commercial names should be stay on English + public static let header = Strings.tr("Localizable", "general.filetype.header", fallback: "Header") + /// File extensions: .htm and .html. Commercial names should be stay on English + public static let htmlDocument = Strings.tr("Localizable", "general.filetype.htmlDocument", fallback: "HTML document") + /// File extension: .iff. Commercial names should be stay on English + public static let iff = Strings.tr("Localizable", "general.filetype.iff", fallback: "Interchange file format") + /// File extension: .iso. Commercial names should be stay on English + public static let iso = Strings.tr("Localizable", "general.filetype.iso", fallback: "ISO image") + /// File extension: .jar. Commercial names should be stay on English + public static let jar = Strings.tr("Localizable", "general.filetype.jar", fallback: "Java archive") + /// File extension: .java. Commercial names should be stay on English + public static let java = Strings.tr("Localizable", "general.filetype.java", fallback: "Java code") + /// File extensions: .jpg and .jpeg. Commercial names should be stay on English + public static let jpeg = Strings.tr("Localizable", "general.filetype.jpeg", fallback: "JPEG image") + /// File extension: .log. Commercial names should be stay on English + public static let log = Strings.tr("Localizable", "general.filetype.log", fallback: "Log") + /// File extension: .3mu. Commercial names should be stay on English + public static let m3u = Strings.tr("Localizable", "general.filetype.m3u", fallback: "Media playlist") + /// File extension: .m4a. Commercial names should be stay on English + public static let m4a = Strings.tr("Localizable", "general.filetype.m4a", fallback: "MPEG-4 audio") + /// File extension: .max. Commercial names should be stay on English + public static let max = Strings.tr("Localizable", "general.filetype.max", fallback: "3ds Max scene") + /// File extension: .mdb. Commercial names should be stay on English + public static let mdb = Strings.tr("Localizable", "general.filetype.mdb", fallback: "MS Access") + /// File extensions: .mid and .midi. Commercial names should be stay on English + public static let mid = Strings.tr("Localizable", "general.filetype.mid", fallback: "MIDI audio") + /// File extension: .mkv. Commercial names should be stay on English + public static let mkv = Strings.tr("Localizable", "general.filetype.mkv", fallback: "MKV video") + /// File extension: .mov. Commercial names should be stay on English + public static let mov = Strings.tr("Localizable", "general.filetype.mov", fallback: "QuickTime movie") + /// File extension: .mp3. Commercial names should be stay on English + public static let mp3 = Strings.tr("Localizable", "general.filetype.mp3", fallback: "MP3 audio") + /// File extension: .mp4. Commercial names should be stay on English + public static let mp4 = Strings.tr("Localizable", "general.filetype.mp4", fallback: "MP4 video") + /// File extensions: .mpeg and .mpg. Commercial names should be stay on English + public static let mpeg = Strings.tr("Localizable", "general.filetype.mpeg", fallback: "MPEG movie") + /// File extension: .msi. Commercial names should be stay on English + public static let msi = Strings.tr("Localizable", "general.filetype.msi", fallback: "MS installer") + /// File extension: .otf. Commercial names should be stay on English + public static let otf = Strings.tr("Localizable", "general.filetype.otf", fallback: "OpenType font") + /// File extension: .pages. Commercial names should be stay on English + public static let pages = Strings.tr("Localizable", "general.filetype.pages", fallback: "Pages") + /// File extension: .pdf. Commercial names should be stay on English + public static let pdf = Strings.tr("Localizable", "general.filetype.pdf", fallback: "PDF document") + /// File extension: .php, .php3, .php4 and .php5. Commercial names should be stay on English + public static let php = Strings.tr("Localizable", "general.filetype.php", fallback: "PHP code") + /// File extension: .pl. Is not a commercial name. "script" is a common noun + public static let pl = Strings.tr("Localizable", "general.filetype.pl", fallback: "Perl script") + /// File extension: .pls. Commercial names should be stay on English + public static let pls = Strings.tr("Localizable", "general.filetype.pls", fallback: "Audio playlist") + /// File extension: .png. Commercial names should be stay on English + public static let png = Strings.tr("Localizable", "general.filetype.png", fallback: "PNG image") + /// File extension: .pcast. Commercial names should be stay on English + public static let podcast = Strings.tr("Localizable", "general.filetype.podcast", fallback: "Podcast") + /// File extension: .py. Is not a commercial name. "script" is a common noun + public static let py = Strings.tr("Localizable", "general.filetype.py", fallback: "Python script") + /// File extension: .rar. Commercial names should be stay on English + public static let rar = Strings.tr("Localizable", "general.filetype.rar", fallback: "RAR compressed") + /// File extensions: .3fr, .arw, .cr2, .dcr, .fff, .mef, .mrw, .nef, .orf, .pef and .rwl. Commercial names should be stay on English + public static let rawImage = Strings.tr("Localizable", "general.filetype.rawImage", fallback: "RAW image") + /// File extension: .rtf. Commercial names should be stay on English + public static let rtf = Strings.tr("Localizable", "general.filetype.rtf", fallback: "Rich text") + /// File extension: .rw2. Commercial names should be stay on English + public static let rw2 = Strings.tr("Localizable", "general.filetype.rw2", fallback: "Panasonic RAW image") + /// File extension: .sh. Is not a commercial name. "shell" is a common noun + public static let sh = Strings.tr("Localizable", "general.filetype.sh", fallback: "Bash shell") + /// File extension: .shmtl. Commercial names should be stay on English + public static let shtml = Strings.tr("Localizable", "general.filetype.shtml", fallback: "Server HTML") + /// File extension: .sitx. Commercial names should be stay on English + public static let sitx = Strings.tr("Localizable", "general.filetype.sitx", fallback: "X compressed") + /// File extensions: .gsheet, .ods and .ots. Commercial names should be stay on English + public static let spreadsheet = Strings.tr("Localizable", "general.filetype.spreadsheet", fallback: "Spreadsheet") + /// File extension: .sql. Commercial names should be stay on English + public static let sql = Strings.tr("Localizable", "general.filetype.sql", fallback: "SQL statements") + /// File extension: .srf. Commercial names should be stay on English + public static let srf = Strings.tr("Localizable", "general.filetype.srf", fallback: "Sony RAW image") + /// File extension: .srt. Commercial names should be stay on English + public static let subtitle = Strings.tr("Localizable", "general.filetype.subtitle", fallback: "Subtitle") + /// File extension: .swf. Commercial names should be stay on English + public static let swf = Strings.tr("Localizable", "general.filetype.swf", fallback: "Flash movie") + /// File extension: .tar. Commercial names should be stay on English + public static let tar = Strings.tr("Localizable", "general.filetype.tar", fallback: "Archive") + /// File extensions: .txt and .odt. Commercial names should be stay on English + public static let textDocument = Strings.tr("Localizable", "general.filetype.textDocument", fallback: "Text document") + /// File extension: .tga. Is not a commercial name. "graphic" is a common noun + public static let tga = Strings.tr("Localizable", "general.filetype.tga", fallback: "TARGA graphic") + /// File extension: .tif. Commercial names should be stay on English + public static let tif = Strings.tr("Localizable", "general.filetype.tif", fallback: "TIF image") + /// File extension: .tiff. Commercial names should be stay on English + public static let tiff = Strings.tr("Localizable", "general.filetype.tiff", fallback: "TIFF image") + /// File extension: .ttf. Commercial names should be stay on English + public static let ttf = Strings.tr("Localizable", "general.filetype.ttf", fallback: "TrueType font") + /// File extension: .svg and .svgz. Commercial names should be stay on English + public static let vectorImage = Strings.tr("Localizable", "general.filetype.vectorImage", fallback: "Vector Image") + /// File extension: .wav. Commercial names should be stay on English + public static let wav = Strings.tr("Localizable", "general.filetype.wav", fallback: "Wave audio") + /// File extension: .webm. Commercial names should be stay on English + public static let webm = Strings.tr("Localizable", "general.filetype.webm", fallback: "WebM video") + /// File extension: .wma. Commercial names should be stay on English + public static let wma = Strings.tr("Localizable", "general.filetype.wma", fallback: "WM audio") + /// File extension: .wmv. Commercial names should be stay on English + public static let wmv = Strings.tr("Localizable", "general.filetype.wmv", fallback: "WM video") + /// File extension: .dotx. Commercial names should be stay on English + public static let wordTemplate = Strings.tr("Localizable", "general.filetype.wordTemplate", fallback: "MS Word template") + /// File extension: .xml. Commercial names should be stay on English + public static let xml = Strings.tr("Localizable", "general.filetype.xml", fallback: "XML document") + /// File extension: .zip. Commercial names should be stay on English + public static let zip = Strings.tr("Localizable", "general.filetype.zip", fallback: "ZIP archive") + } + public enum Format { + /// Plural format key: "%#@count@" + public static func itemsSelected(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.itemsSelected", p1, fallback: "Plural format key: \"%#@count@\"") + } + public enum Count { + /// Plural format key: "%#@file@" + public static func file(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.count.file", p1, fallback: "Plural format key: \"%#@file@\"") + } + /// Plural format key: "%#@folder@" + public static func folder(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.count.folder", p1, fallback: "Plural format key: \"%#@folder@\"") + } + /// Plural format key: "%#@item@" + public static func items(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.count.items", p1, fallback: "Plural format key: \"%#@item@\"") + } + public enum FolderAndFile { + /// Plural format key: "%#@file@" + public static func file(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.count.folderAndFile.file", p1, fallback: "Plural format key: \"%#@file@\"") + } + /// Plural format key: "%#@folder@" + public static func folder(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.count.folderAndFile.folder", p1, fallback: "Plural format key: \"%#@folder@\"") + } + } + } + public enum RetentionPeriod { + /// Plural format key: "%#@day@" + public static func day(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.retentionPeriod.day", p1, fallback: "Plural format key: \"%#@day@\"") + } + /// Plural format key: "%#@hour@" + public static func hour(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.retentionPeriod.hour", p1, fallback: "Plural format key: \"%#@hour@\"") + } + /// Plural format key: "%#@month@" + public static func month(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.retentionPeriod.month", p1, fallback: "Plural format key: \"%#@month@\"") + } + /// Plural format key: "%#@week@" + public static func week(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.retentionPeriod.week", p1, fallback: "Plural format key: \"%#@week@\"") + } + /// Plural format key: "%#@year@" + public static func year(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.retentionPeriod.year", p1, fallback: "Plural format key: \"%#@year@\"") + } + } + public enum RubbishBin { + /// Plural format key: "%#@days@" + public static func days(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.format.rubbishBin.days", p1, fallback: "Plural format key: \"%#@days@\"") + } + } + } + public enum MenuAction { + /// Title for action that allows user to delete files or folders permanently from Rubbish Bin + public static let deletePermanently = Strings.tr("Localizable", "general.menuAction.deletePermanently", fallback: "Delete permanently") + /// Title for files or folders action that allows user to 'Move to Rubbish Bin' + public static let moveToRubbishBin = Strings.tr("Localizable", "general.menuAction.moveToRubbishBin", fallback: "Move to Rubbish bin") + public enum ExportFile { + /// Plural format key: "%#@file@" + public static func title(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.menuAction.exportFile.title", p1, fallback: "Plural format key: \"%#@file@\"") + } + } + public enum ManageLink { + /// Plural format key: "%#@link@" + public static func title(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.menuAction.manageLink.title", p1, fallback: "Plural format key: \"%#@link@\"") + } + } + public enum RemoveLink { + /// Plural format key: "%#@link@" + public static func title(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.menuAction.removeLink.title", p1, fallback: "Plural format key: \"%#@link@\"") + } + public enum DoubleCheck { + public enum Warning { + /// Plural format key: "%#@link@" + public static func message(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.menuAction.removeLink.doubleCheck.warning.message", p1, fallback: "Plural format key: \"%#@link@\"") + } + /// Plural format key: "%#@link@" + public static func title(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.menuAction.removeLink.doubleCheck.warning.title", p1, fallback: "Plural format key: \"%#@link@\"") + } + } + } + public enum Message { + /// Plural format key: "%#@link@" + public static func success(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.menuAction.removeLink.message.success", p1, fallback: "Plural format key: \"%#@link@\"") + } + } + } + public enum ShareFolder { + /// Plural format key: "%#@folder@" + public static func title(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.menuAction.shareFolder.title", p1, fallback: "Plural format key: \"%#@folder@\"") + } + } + public enum ShareLink { + /// Plural format key: "%#@link@" + public static func title(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.menuAction.shareLink.title", p1, fallback: "Plural format key: \"%#@link@\"") + } + } + public enum VerifyContact { + /// Context menu item. Allows the user to verify contacts of the shared folder. The %@ placeholder will be replaced with the contact's name + public static func title(_ p1: Any) -> String { + return Strings.tr("Localizable", "general.menuAction.verifyContact.title", String(describing: p1), fallback: "Approve %@") + } + } + } + public enum SaveToPhotos { + /// Plural format key: "%#@count@" + public static func started(_ p1: Int) -> String { + return Strings.tr("Localizable", "general.saveToPhotos.started", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum Security { + /// Name for the recovery key file + public static let recoveryKeyFile = Strings.tr("Localizable", "general.security.recoveryKeyFile", fallback: "MEGA-RECOVERYKEY") + } + public enum TextEditor { + public enum Hud { + /// Hud info message when edit large text file. + public static let uneditableLargeFile = Strings.tr("Localizable", "general.textEditor.hud.uneditableLargeFile", fallback: "File is too large and cannot be edited.") + /// Hud info message when read unknown encode file. + public static let unknownEncode = Strings.tr("Localizable", "general.textEditor.hud.unknownEncode", fallback: "File could not be edited due to unknown content encoding.") + } + } + } + public enum Help { + public enum ReportIssue { + /// Label to indicate the user that needs to describe the issue through the text edit field of bug report form. + public static let describe = Strings.tr("Localizable", "help.reportIssue.describe", fallback: "Please clearly describe the issue you encountered. The more details you provide, the easier it will be for us to resolve. Your submission will be reviewed by our development team.") + /// Title of the dialog button to discard a report. + public static let discardReport = Strings.tr("Localizable", "help.reportIssue.discardReport", fallback: "Discard report") + /// Switch to confirm the attachment and upload of log files generated by iOS to our support team. + public static let sendLogFile = Strings.tr("Localizable", "help.reportIssue.sendLogFile", fallback: "Send log file") + /// Title of the dialog used to send bug reports to support team. + public static let title = Strings.tr("Localizable", "help.reportIssue.title", fallback: "Report issue") + /// Title to indicate that the bug report is being uploaded to our support team. + public static let uploadingLogFile = Strings.tr("Localizable", "help.reportIssue.uploadingLogFile", fallback: "Uploading log file") + public enum AttachLogFiles { + /// Alert message to confirm if the users want to attach log files generated by iOS to our support team. + public static let message = Strings.tr("Localizable", "help.reportIssue.attachLogFiles.message", fallback: "Do you want to attach diagnostic log files to assist with debug?") + /// Alert title to confirm if the users want to attach log files generated by iOS to our support team. + public static let title = Strings.tr("Localizable", "help.reportIssue.attachLogFiles.title", fallback: "Attach log files") + } + public enum Creating { + public enum Cancel { + /// Confirmation message shown when the user is trying to cancel the ongoing upload report. + public static let message = Strings.tr("Localizable", "help.reportIssue.creating.cancel.message", fallback: "This issue will not be reported if you cancel uploading it.") + /// Confirmation title shown when the user is trying to cancel the ongoing upload report. + public static let title = Strings.tr("Localizable", "help.reportIssue.creating.cancel.title", fallback: "Are you sure you want to cancel uploading your reported issue?") + } + } + public enum DescribeIssue { + /// Placeholder to indicate the user that needs to describe the issue through the text edit field of bug report form. + public static let placeholder = Strings.tr("Localizable", "help.reportIssue.describeIssue.placeholder", fallback: "Describe the issue") + } + public enum Fail { + /// Error message shown when some error occurs during uploading a bug report. + public static let message = Strings.tr("Localizable", "help.reportIssue.fail.message", fallback: "Unable to submit your report. Please try again.") + } + public enum Success { + /// Confirmation message shown when a bug report is successfully uploaded. + public static let message = Strings.tr("Localizable", "help.reportIssue.success.message", fallback: "Thanks. We’ll look into this issue and a member of our team will get back to you.") + /// Confirmation title shown when a bug report is successfully uploaded. + public static let title = Strings.tr("Localizable", "help.reportIssue.success.title", fallback: "Thanks for your feedback") + } + } + } + public enum Home { + public enum Favourites { + /// Title of Favourites explorer view card on Home + public static let title = Strings.tr("Localizable", "home.favourites.title", fallback: "Favourites") + } + public enum Images { + /// Photo Explorer Screen: No images in the account + public static let empty = Strings.tr("Localizable", "home.images.empty", fallback: "No images found") + /// Image title for the photo explorer + public static let title = Strings.tr("Localizable", "home.images.title", fallback: "Images") + } + public enum Recent { + /// Label that indicates who uploaded a file into a recents bucket. %@ is a placeholder for a name, eg: Haley + public static func createdByLabel(_ p1: Any) -> String { + return Strings.tr("Localizable", "home.recent.createdByLabel", String(describing: p1), fallback: "Created by %@") + } + /// Label that indicates who modified a file into a recents bucket. %@ is a placeholder for a name, eg: Haley + public static func modifiedByLabel(_ p1: Any) -> String { + return Strings.tr("Localizable", "home.recent.modifiedByLabel", String(describing: p1), fallback: "Modified by %@") + } + /// Title for a recent action bucket with multiple files. %@ will be replaced by the filename of the first file in the bucket. %ld will be replaced by the total number of other files in the bucket. + public static func multipleFileTitle(_ p1: Any, _ p2: Int) -> String { + return Strings.tr("Localizable", "home.recent.multipleFileTitle", String(describing: p1), p2, fallback: "“%@” and %ld more") + } + } + } + public enum InAppPurchase { + public enum Error { + public enum Alert { + /// Button that redirect you to the main page of your Account in the App Store + public static let primaryButtonTitle = Strings.tr("Localizable", "inAppPurchase.error.alert.primaryButtonTitle", fallback: "App Store settings") + public enum Title { + /// Alert title shown when a selected plan is not available at that moment on the App store + public static let notAvailable = Strings.tr("Localizable", "inAppPurchase.error.alert.title.notAvailable", fallback: "This In-App Purchase item is not available in the App Store at this time. Please verify payment settings for your Apple ID.") + } + } + } + public enum ProductDetail { + public enum Navigation { + /// A label which shows the user's current PRO plan. + public static let currentPlan = Strings.tr("Localizable", "inAppPurchase.productDetail.navigation.currentPlan", fallback: "Current plan") + } + } + public enum Upgrade { + public enum Label { + /// Text shown on the upgrade account page above the current PRO plan subscription + public static let currentPlan = Strings.tr("Localizable", "inAppPurchase.upgrade.label.currentPlan", fallback: "Current plan:") + } + } + } + public enum Inapp { + public enum Notifications { + public enum Meetings { + /// In App notification for meetings + public static let header = Strings.tr("Localizable", "inapp.notifications.meetings.header", fallback: "Meetings") + } + public enum ScheduledMeetings { + public enum OneOff { + public enum Cancelled { + /// In app notification description for one off cancelled meeting + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.oneOff.cancelled.description", fallback: "[B][Email] cancelled[/B] the meeting scheduled for") + } + public enum DayChanged { + /// In app notification description representing the day was changed for one off meeting + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.oneOff.dayChanged.description", fallback: "[B][Email] updated[/B] the meeting date to") + } + public enum DescriptionFieldUpdate { + /// In app notification description for scheduled meeting description field update + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.oneOff.descriptionFieldUpdate.description", fallback: "[B][Email] updated[/B] the meeting description") + } + public enum MulitpleFieldsUpdate { + /// In app notification description for scheduled meeting when multiple fields updated for one off meeting + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.oneOff.mulitpleFieldsUpdate.description", fallback: "[B][Email] updated[/B] the meeting to") + } + public enum New { + /// In app notification description for one off new meeting invitation + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.oneOff.new.description", fallback: "[B][Email] invited[/B] you to a meeting scheduled for:") + } + public enum TimeChanged { + /// In app notification description representing the time was changed for one off meeting + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.oneOff.timeChanged.description", fallback: "[B][Email] updated[/B] the meeting time to") + } + } + public enum Recurring { + public enum Cancelled { + /// In app notification description for cancelled recurring meeting + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.recurring.cancelled.description", fallback: "[B][Email] cancelled[/B] the meeting and all its occurrences") + } + public enum DayChanged { + /// In app notification description when date field is updated for recurring meeting. + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.recurring.dayChanged.description", fallback: "[B][Email] updated[/B] the recurring meeting date to") + } + public enum DescriptionFieldUpdate { + /// In app notification description for description Field update in recurring meeting. + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.recurring.descriptionFieldUpdate.description", fallback: "[B][Email] updated[/B] the recurring meeting description") + } + public enum MulitpleFieldsUpdate { + /// In app notification description for multiple fields update in recurring meeting. + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.recurring.mulitpleFieldsUpdate.description", fallback: "[B][Email] updated[/B] the meeting to") + } + public enum New { + /// In app notification description for recurring new meeting invitation. + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.recurring.new.description", fallback: "[B][Email][/B] invited you to a recurring meeting scheduled for:") + } + public enum OccurrenceCancelled { + /// In app notification description when an occurence is cancelled in a recurring meeting. + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.recurring.occurrenceCancelled.description", fallback: "[B][Email] cancelled[/B] the occurrence scheduled for") + } + public enum OccurrenceUpdated { + /// In app notification description when an occurence is updated in a recurring meeting. + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.recurring.occurrenceUpdated.description", fallback: "[B][Email] updated[/B] an occurrence to") + } + public enum TimeChanged { + /// In app notification description when time field is updated for recurring meeting. + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.recurring.timeChanged.description", fallback: "[B][Email] updated[/B] the recurring meeting time to") + } + } + public enum TitleUpdate { + /// In app notification description for scheduled meeting title update. + public static let description = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.titleUpdate.description", fallback: "[B][Email] updated[/B] the meeting name from “[PreviousTitle]” to [B]“[UpdatedTitle]”[/B]") + } + public enum WeekDay { + public enum MidSentence { + public enum Fri { + /// In app notification description text representing the meeting repeats every friday. This text is used in the middle of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.midSentence.fri.title", fallback: "Fri") + } + public enum Mon { + /// In app notification description text representing the meeting repeats every monday. This text is used in the middle of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.midSentence.mon.title", fallback: "Mon") + } + public enum Sat { + /// In app notification description text representing the meeting repeats every saturday. This text is used in the middle of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.midSentence.sat.title", fallback: "Sat") + } + public enum Sun { + /// In app notification description text representing the meeting repeats every sunday. This text is used in the middle of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.midSentence.sun.title", fallback: "Sun") + } + public enum Thu { + /// In app notification description text representing the meeting repeats every thursday. This text is used in the middle of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.midSentence.thu.title", fallback: "Thu") + } + public enum Tue { + /// In app notification description text representing the meeting repeats every tuesday. This text is used in the middle of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.midSentence.tue.title", fallback: "Tue") + } + public enum Wed { + /// In app notification description text representing the meeting repeats every wednesday. This text is used in the middle of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.midSentence.wed.title", fallback: "Wed") + } + } + public enum SentenceStart { + public enum Fri { + /// In app notification description text representing the meeting repeats every friday. This text is used at the start of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.sentenceStart.fri.title", fallback: "Fri") + } + public enum Mon { + /// In app notification description text representing the meeting repeats every monday. This text is used at the start of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.sentenceStart.mon.title", fallback: "Mon") + } + public enum Sat { + /// In app notification description text representing the meeting repeats every saturday. This text is used at the start of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.sentenceStart.sat.title", fallback: "Sat") + } + public enum Sun { + /// In app notification description text representing the meeting repeats every sunday. This text is used at the start of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.sentenceStart.sun.title", fallback: "Sun") + } + public enum Thu { + /// In app notification description text representing the meeting repeats every thursday. This text is used at the start of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.sentenceStart.thu.title", fallback: "Thu") + } + public enum Tue { + /// In app notification description text representing the meeting repeats every tuesday. This text is used at the start of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.sentenceStart.tue.title", fallback: "Tue") + } + public enum Wed { + /// In app notification description text representing the meeting repeats every wednesday. This text is used at the start of the sentence. + public static let title = Strings.tr("Localizable", "inapp.notifications.scheduledMeetings.weekDay.sentenceStart.wed.title", fallback: "Wed") + } + } + } + } + } + } + public enum Invite { + public enum ContactLink { + public enum Share { + /// Text title showed when users share the contact link + public static let title = Strings.tr("Localizable", "invite.contactLink.share.title", fallback: "Send invitation") + } + } + } + public enum Media { + public enum Audio { + public enum PlaybackContinuation { + public enum Dialog { + /// Description of Audio Player dialog for Playback Continuation + public static func description(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "media.audio.playbackContinuation.dialog.description", String(describing: p1), String(describing: p2), fallback: "“%@” will resume from %@.") + } + /// Restart option for Audio Player dialog for Playback Continuation + public static let restart = Strings.tr("Localizable", "media.audio.playbackContinuation.dialog.restart", fallback: "Restart") + /// Resume option for Audio Player dialog for Playback Continuation + public static let resume = Strings.tr("Localizable", "media.audio.playbackContinuation.dialog.resume", fallback: "Resume") + /// Title of Audio Player dialog for Playback Continuation + public static let title = Strings.tr("Localizable", "media.audio.playbackContinuation.dialog.title", fallback: "Resume audio?") + } + } + public enum Playlist { + public enum Section { + public enum Next { + /// Title of section Next on Audio Playlist + public static let title = Strings.tr("Localizable", "media.audio.playlist.section.next.title", fallback: "Next") + } + } + } + } + public enum Photo { + public enum Browser { + /// Plural format key: "%#@total@" + public static func indexOfTotalFiles(_ p1: Int) -> String { + return Strings.tr("Localizable", "media.photo.browser.indexOfTotalFiles", p1, fallback: "Plural format key: \"%#@total@\"") + } + } + } + public enum PhotoLibrary { + public enum Category { + public enum All { + /// Title of Photo's All date tab + public static let title = Strings.tr("Localizable", "media.photoLibrary.category.all.title", fallback: "All") + } + public enum Days { + /// Title of Photo's Days tab + public static let title = Strings.tr("Localizable", "media.photoLibrary.category.days.title", fallback: "Days") + } + public enum Months { + /// Title of Photo's Months tab + public static let title = Strings.tr("Localizable", "media.photoLibrary.category.months.title", fallback: "Months") + } + public enum Years { + /// Title of Photo's Years tab + public static let title = Strings.tr("Localizable", "media.photoLibrary.category.years.title", fallback: "Years") + } + } + } + public enum Quality { + /// Automatic quality option used on Chat Image uploads. Indicating that the image quality will be determine by MEGA. + public static let automatic = Strings.tr("Localizable", "media.quality.automatic", fallback: "Automatic") + /// Best quality option for exporting file in Scan document. + public static let best = Strings.tr("Localizable", "media.quality.best", fallback: "Best") + /// High quality option used on CU and Chat Video uploads. + public static let high = Strings.tr("Localizable", "media.quality.high", fallback: "High") + /// Low quality option used on CU and Chat Video uploads and exporting file in Scan Document. + public static let low = Strings.tr("Localizable", "media.quality.low", fallback: "Low") + /// Medium quality option used on CU and Chat Video uploads and exporting file in Scan Document. + public static let medium = Strings.tr("Localizable", "media.quality.medium", fallback: "Medium") + /// Optimised quality option used on Chat Images. Indicating that the image will be optimised. + public static let optimised = Strings.tr("Localizable", "media.quality.optimised", fallback: "Optimised") + /// Original quality option used on CU Videos and Chat's image and video uploads. Indicating that the media quality will be the same. + public static let original = Strings.tr("Localizable", "media.quality.original", fallback: "Original") + } + } + public enum Meetings { + /// Message to inform the local user is having a bad quality network with someone in the current group call + public static let poorConnection = Strings.tr("Localizable", "meetings.poorConnection", fallback: "Bad connection") + public enum Action { + /// Action of a call/meeting to rename it + public static let rename = Strings.tr("Localizable", "meetings.action.rename", fallback: "Rename meeting") + /// Action of a call/meeting to share its link + public static let shareLink = Strings.tr("Localizable", "meetings.action.shareLink", fallback: "Share meeting link") + } + public enum AddContacts { + public enum AllContactsAdded { + /// Contacts selection screen: Button title for the alert that is shown when all the contacts are already added to the chatRoom + public static let confirmationButtonTitle = Strings.tr("Localizable", "meetings.addContacts.allContactsAdded.confirmationButtonTitle", fallback: "Invite") + /// Contacts selection screen: Alert message shown when all the contacts are already added to the chatRoom + public static let description = Strings.tr("Localizable", "meetings.addContacts.allContactsAdded.description", fallback: "You’ve already added all your contacts to this chat.\nIf you want to add more participants, first invite them to your contact list.") + /// Contacts selection screen: Alert title shown when all the contacts are already added to the chatRoom + public static let title = Strings.tr("Localizable", "meetings.addContacts.allContactsAdded.title", fallback: "All contacts added") + } + public enum AllowNonHost { + /// Add contacts: Message to allow non host to add contacts in the group chat and meeting + public static let message = Strings.tr("Localizable", "meetings.addContacts.allowNonHost.message", fallback: "Allow non-hosts to add participants") + } + public enum ZeroContactsAvailable { + /// Contacts selection screen: Alert message shown when no contacts are available to be added to the chatRoom + public static let description = Strings.tr("Localizable", "meetings.addContacts.zeroContactsAvailable.description", fallback: "You have no contacts to add to this chat. If you want to add participants, first invite them to your contact list.") + /// Contacts selection screen: Alert title shown when no contacts are available to be added to the chatRoom + public static let title = Strings.tr("Localizable", "meetings.addContacts.zeroContactsAvailable.title", fallback: "No contacts") + } + } + public enum Alert { + /// Shown when an invalid/inexisting/not-available-anymore meeting link is opened. + public static let end = Strings.tr("Localizable", "meetings.alert.end", fallback: "Meeting ended") + /// Meeting ended Alert button -- View Meeting Chat history. + public static let meetingchat = Strings.tr("Localizable", "meetings.alert.meetingchat", fallback: "View chat history") + public enum End { + /// Shown description when an invalid/inexisting/not-available-anymore meeting link is opened. + public static let description = Strings.tr("Localizable", "meetings.alert.end.description", fallback: "The meeting you’re trying to join has already ended. You can still view the meeting chat history.") + } + } + public enum Create { + /// Text button for init a Meeting. + public static let newMeeting = Strings.tr("Localizable", "meetings.create.newMeeting", fallback: "New meeting") + } + public enum CreateMeeting { + /// Meeting Title + public static func defaultMeetingName(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.createMeeting.defaultMeetingName", String(describing: p1), fallback: "%@’s meeting") + } + /// Start meeting button title + public static let startMeeting = Strings.tr("Localizable", "meetings.createMeeting.startMeeting", fallback: "Start meeting") + } + public enum DisplayInMainView { + /// Menu title in meetings that allows to switch to main view for a participant. + public static let title = Strings.tr("Localizable", "meetings.displayInMainView.title", fallback: "Display in main view") + } + public enum EndCall { + /// Button and title text that ends the meeting for all + public static let endForAllButtonTitle = Strings.tr("Localizable", "meetings.endCall.endForAllButtonTitle", fallback: "End call for all") + public enum EndForAllAlert { + /// Button title - tapping on the button dismiss without ending the call. + public static let cancel = Strings.tr("Localizable", "meetings.endCall.endForAllAlert.cancel", fallback: "No") + /// Button title - tapping on the button will end the call. + public static let confirm = Strings.tr("Localizable", "meetings.endCall.endForAllAlert.confirm", fallback: "Yes") + /// Description of the popup that ends the meeting for all + public static let description = Strings.tr("Localizable", "meetings.endCall.endForAllAlert.description", fallback: "This will end the call for all participants.") + /// Title of the popup that ends the meeting for all + public static let title = Strings.tr("Localizable", "meetings.endCall.endForAllAlert.title", fallback: "End call for all?") + } + } + public enum EndCallDialog { + /// Meetings end call dialog description + public static let description = Strings.tr("Localizable", "meetings.endCallDialog.description", fallback: "Call will automatically end in 2 mins unless you want to stay on it.") + /// Meetings end call dialog button title + public static let endCallNowButtonTitle = Strings.tr("Localizable", "meetings.endCallDialog.endCallNowButtonTitle", fallback: "End call now") + /// Meetings end call dialog Stay on call button title + public static let stayOnCallButtonTitle = Strings.tr("Localizable", "meetings.endCallDialog.stayOnCallButtonTitle", fallback: "Stay on call") + /// Meetings end call dialog title + public static let title = Strings.tr("Localizable", "meetings.endCallDialog.title", fallback: "You’re the only one here") + } + public enum EndForAll { + /// Label for end a group chat or meeting when the user is moderator + public static let buttonTitle = Strings.tr("Localizable", "meetings.endForAll.buttonTitle", fallback: "End for all") + } + public enum EnterMeetingLink { + /// Title for Enter Meeting Link. + public static let title = Strings.tr("Localizable", "meetings.enterMeetingLink.title", fallback: "Enter meeting link") + } + public enum Incompatibility { + /// This message is shown when calling and the receiver is not picking up the call + public static let warningMessage = Strings.tr("Localizable", "meetings.incompatibility.warningMessage", fallback: "We are upgrading MEGA Chat. Your calls might not be connected due to version incompatibility unless all parties update their MEGA Apps to the latest version.") + } + public enum Info { + /// Title for chat notifications option in meeting info + public static let chatNotifications = Strings.tr("Localizable", "meetings.info.chatNotifications", fallback: "Chat notifications") + /// Label to introduce meeting info description field + public static let descriptionLabel = Strings.tr("Localizable", "meetings.info.descriptionLabel", fallback: "Description") + /// The button title that allows the user to leave a ad hoc meeting. + public static let leaveMeeting = Strings.tr("Localizable", "meetings.info.leaveMeeting", fallback: "Leave meeting") + /// Title for manage chat history option in meeting info + public static let manageChatHistory = Strings.tr("Localizable", "meetings.info.manageChatHistory", fallback: "Manage chat history") + /// Title for manage meeting history option in meeting info + public static let manageMeetingHistory = Strings.tr("Localizable", "meetings.info.manageMeetingHistory", fallback: "Manage meeting history") + /// Label to indicate the user about the activation of a meeting link + public static let meetingLink = Strings.tr("Localizable", "meetings.info.meetingLink", fallback: "Meeting link") + /// Title for meeting notifications option in meeting info + public static let meetingNotifications = Strings.tr("Localizable", "meetings.info.meetingNotifications", fallback: "Meeting notifications") + /// Meeting Notifications DND: Title bar message for the dnd activate options + public static let muteMeetingNotificationsFor = Strings.tr("Localizable", "meetings.info.muteMeetingNotificationsFor", fallback: "Mute meeting notifications for") + /// The title of a menu button which allows users to rename a meeting. + public static let renameMeeting = Strings.tr("Localizable", "meetings.info.renameMeeting", fallback: "Rename meeting") + /// Title for share chat link option in meeting info + public static let shareChatLink = Strings.tr("Localizable", "meetings.info.shareChatLink", fallback: "Share chat link") + /// Title for shared files option in meeting info + public static let sharedFiles = Strings.tr("Localizable", "meetings.info.sharedFiles", fallback: "Shared files") + /// Title for share meeting link option in meeting info + public static let shareMeetingLink = Strings.tr("Localizable", "meetings.info.shareMeetingLink", fallback: "Share meeting link") + public enum KeyRotation { + /// Description for enable encryption key rotation option in meeting info + public static let description = Strings.tr("Localizable", "meetings.info.keyRotation.description", fallback: "Key rotation is slightly more secure, but doesn’t allow you to create a chat link and hides past messages from new participants.") + /// Title for enable encryption key rotation option in meeting info + public static let title = Strings.tr("Localizable", "meetings.info.keyRotation.title", fallback: "Enable encryption key rotation") + } + public enum ManageMeetingHistory { + /// The button or alert title to delete the history of a meeting. + public static let clearMeetingHistory = Strings.tr("Localizable", "meetings.info.manageMeetingHistory.clearMeetingHistory", fallback: "Clear meeting history") + /// The alert message to delete the history of a meeting. + public static let clearMeetingHistoryMessage = Strings.tr("Localizable", "meetings.info.manageMeetingHistory.clearMeetingHistoryMessage", fallback: "Are you sure you want to clear the full message history of this meeting?") + /// Message show when the history of a meeting has been successfully deleted + public static let meetingHistoryHasBeenCleared = Strings.tr("Localizable", "meetings.info.manageMeetingHistory.meetingHistoryHasBeenCleared", fallback: "Meeting history cleared") + } + public enum Participants { + /// Button title to load more participants in meeting info view + public static let seeAll = Strings.tr("Localizable", "meetings.info.participants.seeAll", fallback: "See all") + /// Button title to see less participants in meeting info view + public static let seeLess = Strings.tr("Localizable", "meetings.info.participants.seeLess", fallback: "See less") + /// Button title to load more participants in meeting info view + public static let seeMore = Strings.tr("Localizable", "meetings.info.participants.seeMore", fallback: "See more") + } + public enum ShareMeetingLink { + /// Text explaining users how the meeting links work. + public static let explainLink = Strings.tr("Localizable", "meetings.info.shareMeetingLink.explainLink", fallback: "Anyone with this link can join the meeting and view the meeting chat") + } + public enum ShareOptions { + /// Title for a button to send a file to a chat + public static let sendToChat = Strings.tr("Localizable", "meetings.info.shareOptions.sendToChat", fallback: "Send to chat") + /// Label to inform user about privacy of sharing a chat link + public static let title = Strings.tr("Localizable", "meetings.info.shareOptions.title", fallback: "Anyone with this link can join the meeting and view the meeting chat.") + public enum ShareLink { + /// HUD message to inform user that the link is in the clipboard + public static let linkCopied = Strings.tr("Localizable", "meetings.info.shareOptions.shareLink.linkCopied", fallback: "Copied link to clipboard") + } + } + } + public enum JoinMeeting { + /// Message description when the user enters a invalid meeting URL + public static let description = Strings.tr("Localizable", "meetings.joinMeeting.description", fallback: "Unable to join the meeting. Please check the link is valid.") + /// Message header when the user enters a invalid meeting URL + public static let header = Strings.tr("Localizable", "meetings.joinMeeting.header", fallback: "Invalid meeting URL") + } + public enum JoinMega { + /// Encourage Guest User to Join Mega Screen Navigation Bar Title + public static let title = Strings.tr("Localizable", "meetings.joinMega.title", fallback: "Join MEGA") + public enum Paragraph1 { + /// Guest user join Mega: paragraph 1 description + public static let description = Strings.tr("Localizable", "meetings.joinMega.paragraph1.description", fallback: "Join the largest secure cloud storage and collaboration platform in the world.") + /// Guest user join Mega: paragraph 1 title + public static let title = Strings.tr("Localizable", "meetings.joinMega.paragraph1.title", fallback: "Your privacy matters") + } + public enum Paragraph2 { + /// Guest user join Mega: paragraph 2 description + public static let description = Strings.tr("Localizable", "meetings.joinMega.paragraph2.description", fallback: "Sign up now and enjoy advanced collaboration features for free.") + /// Guest user join Mega: paragraph 2 title + public static let title = Strings.tr("Localizable", "meetings.joinMega.paragraph2.title", fallback: "Get 20 GB for free") + } + } + public enum LeaveCall { + /// Label for hang a group chat or meeting when the user is moderator + public static let buttonTitle = Strings.tr("Localizable", "meetings.leaveCall.buttonTitle", fallback: "Leave") + } + public enum Link { + public enum Guest { + /// Button text to join a meeting for the logged in user + public static let joinButtonText = Strings.tr("Localizable", "meetings.link.guest.joinButtonText", fallback: "Join as guest") + } + public enum LoggedInUser { + /// Button text to join a meeting for the logged in user + public static let joinButtonText = Strings.tr("Localizable", "meetings.link.loggedInUser.joinButtonText", fallback: "Join meeting") + } + } + public enum Message { + /// Message to inform the local user that someone has joined the current group call + public static func joinedCall(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.message.joinedCall", String(describing: p1), fallback: "%@ joined the call") + } + /// Message to inform the local user that someone has left the current group call + public static func leftCall(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.message.leftCall", String(describing: p1), fallback: "%@ left the call") + } + /// Message shown when the user is the only one left in the meeting + public static let noOtherParticipants = Strings.tr("Localizable", "meetings.message.noOtherParticipants", fallback: "You are the only one here…") + /// Message shown when a meeting starts and user is waiting other users to join + public static let waitingOthers = Strings.tr("Localizable", "meetings.message.waitingOthers", fallback: "Waiting for others to join…") + } + public enum New { + /// Error text shown when trying to create or join a new meeting given that the user is already in another meeting + public static let anotherAlreadyExistsError = Strings.tr("Localizable", "meetings.new.anotherAlreadyExistsError", fallback: "Another call in progress. Please end your current call before making another.") + public enum AnotherAlreadyExistsError { + /// Button option when trying to join a new meeting given that the user is already in another meeting + public static let endAndJoin = Strings.tr("Localizable", "meetings.new.anotherAlreadyExistsError.endAndJoin", fallback: "End and join") + } + } + public enum Notification { + /// Meetings: Notification message to be shown to the user when the timer is set and call is going to end after the timer duration + public static func endCallTimerDuration(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.notification.EndCallTimerDuration", String(describing: p1), fallback: "Call will end in %@") + } + /// Meetings: Notification message to be shown when more than 2 people are joining the call at the same time. The second %@ would display the number in spell out format. + public static func moreThanTwoUsersJoined(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "meetings.notification.moreThanTwoUsersJoined", String(describing: p1), String(describing: p2), fallback: "%@ and %@ others joined") + } + /// Meetings: Notification message to be shown when more than 2 people are leaving the call at the same time. The second %@ would display the number in spell out format. + public static func moreThanTwoUsersLeft(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "meetings.notification.moreThanTwoUsersLeft", String(describing: p1), String(describing: p2), fallback: "%@ and %@ others left the call") + } + /// Meetings: Notification message to be shown when one person joins the call. + public static func singleUserJoined(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.notification.singleUserJoined", String(describing: p1), fallback: "%@ joined") + } + /// Meetings: Notification message to be shown when one person leaves the call. + public static func singleUserLeft(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.notification.singleUserLeft", String(describing: p1), fallback: "%@ left the call") + } + /// Meetings: Notification message to be shown when two people join the call at the same time + public static func twoUsersJoined(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "meetings.notification.twoUsersJoined", String(describing: p1), String(describing: p2), fallback: "%@ and %@ joined") + } + /// Meetings: Notification message to be shown when two people are leaving the call at the same time + public static func twoUsersLeft(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "meetings.notification.twoUsersLeft", String(describing: p1), String(describing: p2), fallback: "%@ and %@ left the call") + } + } + public enum Notifications { + /// Message shown when the user privilege is changed to moderator + public static let moderatorPrivilege = Strings.tr("Localizable", "meetings.notifications.moderatorPrivilege", fallback: "You are the new host") + } + public enum Panel { + /// Contacts selection screen header: Invite contacts to meetings + public static let inviteParticipants = Strings.tr("Localizable", "meetings.panel.InviteParticipants", fallback: "Invite participants") + /// Meetings: Floating panel participants count header + public static func participantsCount(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.panel.ParticipantsCount", p1, fallback: "Participants (%d)") + } + /// Meetings: Floating panel share link option + public static let shareLink = Strings.tr("Localizable", "meetings.panel.shareLink", fallback: "Share link") + } + public enum Participant { + /// Meetings: Action on a meeting that change participant's role to moderator + public static let makeModerator = Strings.tr("Localizable", "meetings.participant.makeModerator", fallback: "Make host") + /// Meetings: Floating panel participants listing indication if a participant is moderator + public static let moderator = Strings.tr("Localizable", "meetings.participant.moderator", fallback: "Host") + /// Meetings: Action on a meeting that remove participant's moderator role + public static let removeModerator = Strings.tr("Localizable", "meetings.participant.removeModerator", fallback: "Remove as host") + } + public enum QuickAction { + /// This is the button text for flipping the front and back camera in the floating panel for meetings + public static let flip = Strings.tr("Localizable", "meetings.quickAction.flip", fallback: "Switch") + /// This is the button text for enabling the loud speaker in the floating panel for meetings + public static let speaker = Strings.tr("Localizable", "meetings.quickAction.speaker", fallback: "Speaker") + } + public enum Reconnecting { + /// Title shown when the user lost the connection in a call/meeting, and the app is unable to reconnect after 30 seconds. + public static let failed = Strings.tr("Localizable", "meetings.reconnecting.failed", fallback: "Unable to reconnect") + /// Title shown when the user lost the connection in a call/meeting, and the app will try to reconnect the user again. + public static let title = Strings.tr("Localizable", "meetings.reconnecting.title", fallback: "Reconnecting") + } + public enum ScheduleMeeting { + /// Schedule meeting add participants field + public static let addParticipants = Strings.tr("Localizable", "meetings.scheduleMeeting.addParticipants", fallback: "Add participants") + /// Schedule meeting cancel button + public static let cancel = Strings.tr("Localizable", "meetings.scheduleMeeting.cancel", fallback: "Cancel") + /// Schedule meeting create button + public static let create = Strings.tr("Localizable", "meetings.scheduleMeeting.create", fallback: "Create") + /// Schedule meeting description field placeholder + public static let description = Strings.tr("Localizable", "meetings.scheduleMeeting.description", fallback: "Add description") + /// Schedule meeting end field + public static let end = Strings.tr("Localizable", "meetings.scheduleMeeting.end", fallback: "Ends") + /// Schedule meeting link field + public static let link = Strings.tr("Localizable", "meetings.scheduleMeeting.link", fallback: "Meeting link") + /// Schedule meeting creation complete + public static let meetingCreated = Strings.tr("Localizable", "meetings.scheduleMeeting.meetingCreated", fallback: "Meeting created") + /// Schedule meeting open invite field + public static let openInvite = Strings.tr("Localizable", "meetings.scheduleMeeting.openInvite", fallback: "Allow non-hosts to add participants") + /// Schedule meeting recurrence field + public static let recurrence = Strings.tr("Localizable", "meetings.scheduleMeeting.recurrence", fallback: "Recurrence") + /// Schedule meeting send calendar invite field + public static let sendCalendarInvite = Strings.tr("Localizable", "meetings.scheduleMeeting.sendCalendarInvite", fallback: "Send calendar invite") + /// Schedule meeting start field + public static let start = Strings.tr("Localizable", "meetings.scheduleMeeting.start", fallback: "Starts") + /// Schedule meeting view title + public static let title = Strings.tr("Localizable", "meetings.scheduleMeeting.title", fallback: "Schedule meeting") + /// Schedule meeting update button + public static let update = Strings.tr("Localizable", "meetings.scheduleMeeting.update", fallback: "Update") + /// Schedule meeting waiting room setting title + public static let waitingRoom = Strings.tr("Localizable", "meetings.scheduleMeeting.waitingRoom", fallback: "Waiting room") + public enum CalendarInvite { + /// Schedule meeting calendar invite description + public static let description = Strings.tr("Localizable", "meetings.scheduleMeeting.calendarInvite.description", fallback: "Email a calendar invite to participants so they can add the meeting to their calendars.") + } + public enum Create { + public enum Daily { + /// This text is shown to the user while creating a scheduled meeting. User can choose between Daily, weekly and Monthly options. + public static let optionTitle = Strings.tr("Localizable", "meetings.scheduleMeeting.create.daily.optionTitle", fallback: "Daily") + } + public enum EndRecurrence { + /// End Recurrence option title in creating schedule meeting screen. + public static let title = Strings.tr("Localizable", "meetings.scheduleMeeting.create.endRecurrence.title", fallback: "End recurrence") + public enum Option { + /// End Recurrence never option title in creating schedule meeting screen. + public static let never = Strings.tr("Localizable", "meetings.scheduleMeeting.create.endRecurrence.option.never", fallback: "Never") + /// End Recurrence on date option title in creating schedule meeting screen. + public static let onDate = Strings.tr("Localizable", "meetings.scheduleMeeting.create.endRecurrence.option.onDate", fallback: "On date") + } + } + public enum Frequency { + /// Create Schedule Meeting: The text is shown along with the selected frequency which can either be Daily, Weekly and Monthly. + public static let optionTitle = Strings.tr("Localizable", "meetings.scheduleMeeting.create.frequency.optionTitle", fallback: "Frequency") + public enum Picker { + /// Create Schedule Meeting: This text is used as a accessibility label for frequency picker. + public static let accessibilityLabel = Strings.tr("Localizable", "meetings.scheduleMeeting.create.frequency.picker.accessibilityLabel", fallback: "Select a frequency") + } + } + public enum Interval { + /// Create Schedule Meeting: The text is shown along with the selected interval. + public static let optionTitle = Strings.tr("Localizable", "meetings.scheduleMeeting.create.interval.optionTitle", fallback: "Every") + public enum Picker { + /// Create Schedule Meeting: This text is used as a accessibility label for interval picker. + public static let accessibilityLabel = Strings.tr("Localizable", "meetings.scheduleMeeting.create.interval.picker.accessibilityLabel", fallback: "Select a monthly interval") + } + } + public enum Monthly { + /// This text is shown to the user while creating a scheduled meeting. User can choose between Daily, weekly and Monthly options. + public static let optionTitle = Strings.tr("Localizable", "meetings.scheduleMeeting.create.monthly.optionTitle", fallback: "Monthly") + public enum Calendar { + /// Create Schedule Meeting: Month custom option - header title for the calendar + public static let headerTitle = Strings.tr("Localizable", "meetings.scheduleMeeting.create.monthly.calendar.headerTitle", fallback: "Each") + } + public enum WeekNumber { + /// Create Schedule Meeting: Month custom option - fifth week option text + public static let fifth = Strings.tr("Localizable", "meetings.scheduleMeeting.create.monthly.weekNumber.fifth", fallback: "fifth") + /// Create Schedule Meeting: Month custom option - first week option text + public static let first = Strings.tr("Localizable", "meetings.scheduleMeeting.create.monthly.weekNumber.first", fallback: "first") + /// Create Schedule Meeting: Month custom option - fourth week option text + public static let fourth = Strings.tr("Localizable", "meetings.scheduleMeeting.create.monthly.weekNumber.fourth", fallback: "fourth") + /// Create Schedule Meeting: Month custom option - second week option text + public static let second = Strings.tr("Localizable", "meetings.scheduleMeeting.create.monthly.weekNumber.second", fallback: "second") + /// Create Schedule Meeting: Month custom option - third week option text + public static let third = Strings.tr("Localizable", "meetings.scheduleMeeting.create.monthly.weekNumber.third", fallback: "third") + } + public enum WeekNumberAndWeekDay { + /// Create Schedule Meeting: Month custom option - header title for choosing the week number and week day + public static let headerTitle = Strings.tr("Localizable", "meetings.scheduleMeeting.create.monthly.weekNumberAndWeekDay.headerTitle", fallback: "On the…") + } + } + public enum MonthlyRecurrenceOption { + public enum DayThirtyFirstSelected { + /// Create Schedule Meeting: Footnote shown to the user when the day selected is 31 of the month and the recurrence option is monthly. + public static let footNote = Strings.tr("Localizable", "meetings.scheduleMeeting.create.monthlyRecurrenceOption.DayThirtyFirstSelected.footNote", fallback: "Some months don’t have 31 days. For these months, the occurrence will be scheduled for the last day of the month.") + } + public enum DayThirtySelected { + /// Create Schedule Meeting: Footnote shown to the user when the day selected is 30 of the month and the recurrence option is monthly. + public static let footNote = Strings.tr("Localizable", "meetings.scheduleMeeting.create.monthlyRecurrenceOption.DayThirtySelected.footNote", fallback: "Some months don’t have 30 days. For these months, the occurrence will be scheduled for the last day of the month.") + } + public enum DayTwentyNineSelected { + /// Create Schedule Meeting: Footnote shown to the user when the day selected is 29 of the month and the recurrence option is monthly. + public static let footNote = Strings.tr("Localizable", "meetings.scheduleMeeting.create.monthlyRecurrenceOption.DayTwentyNineSelected.footNote", fallback: "Some months don’t have 29 days. For these months, the occurrence will be scheduled for the last day of the month.") + } + } + public enum RecurrenceOptionScreen { + /// Recurrence option custom shown in the recurrence options screen while creating the schedule meeting. + public static let custom = Strings.tr("Localizable", "meetings.scheduleMeeting.create.recurrenceOptionScreen.custom", fallback: "Custom") + /// Recurrence option daily shown in the recurrence options screen while creating the schedule meeting. + public static let daily = Strings.tr("Localizable", "meetings.scheduleMeeting.create.recurrenceOptionScreen.daily", fallback: "Every day") + /// Recurrence option monthly shown in the recurrence options screen while creating the schedule meeting. + public static let monthly = Strings.tr("Localizable", "meetings.scheduleMeeting.create.recurrenceOptionScreen.monthly", fallback: "Every month") + /// Navigation title of the recurrence options screen while creating the schedule meeting. + public static let navigationTitle = Strings.tr("Localizable", "meetings.scheduleMeeting.create.recurrenceOptionScreen.navigationTitle", fallback: "Recurrence") + /// Recurrence option never shown in the recurrence options screen while creating the schedule meeting. + public static let never = Strings.tr("Localizable", "meetings.scheduleMeeting.create.recurrenceOptionScreen.never", fallback: "Never") + /// Recurrence option weekly shown in the recurrence options screen while creating the schedule meeting. + public static let weekly = Strings.tr("Localizable", "meetings.scheduleMeeting.create.recurrenceOptionScreen.weekly", fallback: "Every week") + } + public enum SelectedRecurrence { + public enum Daily { + /// Plural format key: "%#@count@" + public static func customInterval(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduleMeeting.create.selectedRecurrence.daily.customInterval", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum Weekly { + /// Create Schedule Meeting: Schedule meeting repeats all days in a week. This text is shown in the main screen that shows all the options along with the selected recurrence option. + public static let everyDay = Strings.tr("Localizable", "meetings.scheduleMeeting.create.selectedRecurrence.weekly.everyDay", fallback: "Daily") + } + } + public enum SelectedRecurrenceOption { + /// Recurrence option daily shown in the creating the schedule meeting screen as the selected option. + public static let daily = Strings.tr("Localizable", "meetings.scheduleMeeting.create.selectedRecurrenceOption.daily", fallback: "Daily") + /// Recurrence option monthly shown in the creating the schedule meeting screen as the selected option. + public static let monthly = Strings.tr("Localizable", "meetings.scheduleMeeting.create.selectedRecurrenceOption.monthly", fallback: "Monthly") + /// Recurrence option never shown in the creating the schedule meeting screen as the selected option. + public static let never = Strings.tr("Localizable", "meetings.scheduleMeeting.create.selectedRecurrenceOption.never", fallback: "Never") + /// Recurrence option weekly shown in the creating the schedule meeting screen as the selected option. + public static let weekly = Strings.tr("Localizable", "meetings.scheduleMeeting.create.selectedRecurrenceOption.weekly", fallback: "Weekly") + } + public enum WeekDay { + public enum Picker { + /// Create Schedule Meeting: This text is used as a accessibility label for week day picker. + public static let accessibilityLabel = Strings.tr("Localizable", "meetings.scheduleMeeting.create.weekDay.picker.accessibilityLabel", fallback: "Select a day of the week") + } + } + public enum WeekNumber { + public enum Picker { + /// Create Schedule Meeting: This text is used as a accessibility label for week number picker. + public static let accessibilityLabel = Strings.tr("Localizable", "meetings.scheduleMeeting.create.weekNumber.picker.accessibilityLabel", fallback: "Select a weekly interval") + } + } + public enum Weekly { + /// This text is shown to the user while creating a scheduled meeting. User can choose between Daily, weekly and Monthly options. + public static let optionTitle = Strings.tr("Localizable", "meetings.scheduleMeeting.create.weekly.optionTitle", fallback: "Weekly") + } + } + public enum CreateMeetingTip { + /// Create meeting tip view message + public static let message = Strings.tr("Localizable", "meetings.scheduleMeeting.createMeetingTip.message", fallback: "You can now schedule one-off and recurring meetings.") + /// Create meeting tip view title + public static let title = Strings.tr("Localizable", "meetings.scheduleMeeting.createMeetingTip.title", fallback: "Schedule meeting") + } + public enum Description { + /// Schedule meeting description error + public static let lenghtError = Strings.tr("Localizable", "meetings.scheduleMeeting.description.lenghtError", fallback: "Enter up to 3,000 characters") + } + public enum DiscardChanges { + /// Schedule meeting discard changes cancellation + public static let cancel = Strings.tr("Localizable", "meetings.scheduleMeeting.discardChanges.cancel", fallback: "Keep editing") + /// Schedule meeting discard changes confirmation + public static let confirm = Strings.tr("Localizable", "meetings.scheduleMeeting.discardChanges.confirm", fallback: "Discard changes") + /// Schedule meeting discard changes title + public static let title = Strings.tr("Localizable", "meetings.scheduleMeeting.discardChanges.title", fallback: "Discard changes or keep editing?") + } + public enum Link { + /// Schedule meeting end field + public static let description = Strings.tr("Localizable", "meetings.scheduleMeeting.link.description", fallback: "Anyone with this link can join the meeting and view the meeting chat.") + } + public enum MeetingName { + /// Schedule meeting name error + public static let lenghtError = Strings.tr("Localizable", "meetings.scheduleMeeting.meetingName.lenghtError", fallback: "Enter up to 30 characters") + /// Schedule meeting name field placeholder + public static let placeholder = Strings.tr("Localizable", "meetings.scheduleMeeting.meetingName.placeholder", fallback: "Meeting name") + } + public enum Notification { + public enum MeetingStarts { + public enum Button { + /// Push notification: Button title to be shown when the meeting is about to start. + public static let join = Strings.tr("Localizable", "meetings.scheduleMeeting.notification.meetingStarts.button.join", fallback: "Join") + /// Push notification: Button title to be shown when the meeting is about to start. + public static let message = Strings.tr("Localizable", "meetings.scheduleMeeting.notification.meetingStarts.button.message", fallback: "Message") + } + } + public enum MeetingStartsInFifteenMins { + /// Push notification message to be shown when the meeting is about to start in 15 min. + public static let message = Strings.tr("Localizable", "meetings.scheduleMeeting.notification.meetingStartsInFifteenMins.message", fallback: "Meeting starts in 15 minutes") + } + public enum MeetingStartsNow { + /// Push notification message to be shown when the meeting is about to start now. + public static let message = Strings.tr("Localizable", "meetings.scheduleMeeting.notification.meetingStartsNow.message", fallback: "Meeting starts now") + } + } + public enum Occurrence { + public enum UpdateSuccessfull { + /// Schedule meeting update: Success message shown when the schedule meeeting occurrence is updated successfully. + public static let popupMessage = Strings.tr("Localizable", "meetings.scheduleMeeting.occurrence.updateSuccessfull.popupMessage", fallback: "Meeting occurrence updated") + } + } + public enum RecurringMeetingTip { + /// Recurring meeting tip view message + public static let message = Strings.tr("Localizable", "meetings.scheduleMeeting.recurringMeetingTip.message", fallback: "You can view, cancel, or edit any occurrence of a recurring meeting by tapping and holding the meeting and selecting Occurrences.") + /// Recurring meeting tip view message bold text + public static let occurrences = Strings.tr("Localizable", "meetings.scheduleMeeting.recurringMeetingTip.occurrences", fallback: "Occurrences") + /// Recurring meeting tip view title + public static let title = Strings.tr("Localizable", "meetings.scheduleMeeting.recurringMeetingTip.title", fallback: "Manage recurring meetings") + } + public enum StartMeetingTip { + /// Start meeting tip view message + public static let message = Strings.tr("Localizable", "meetings.scheduleMeeting.startMeetingTip.message", fallback: "You can start the meeting before its scheduled time by tapping “Start meeting” in the meeting room.") + /// Start meeting tip view message bold text + public static let startMeeting = Strings.tr("Localizable", "meetings.scheduleMeeting.startMeetingTip.startMeeting", fallback: "Start meeting") + /// Start meeting tip view title + public static let title = Strings.tr("Localizable", "meetings.scheduleMeeting.startMeetingTip.title", fallback: "Start meeting") + } + public enum TipView { + /// Scheduled meeting tip view dismiss action button title + public static let gotIt = Strings.tr("Localizable", "meetings.scheduleMeeting.tipView.gotIt", fallback: "Got it") + } + public enum UpdateSuccessfull { + /// Schedule meeting update: Success message shown when the schedule meeeting is updated successfully. + public static let popupMessage = Strings.tr("Localizable", "meetings.scheduleMeeting.updateSuccessfull.popupMessage", fallback: "Meeting updated") + } + public enum WaitingRoom { + /// Schedule meeting waiting room setting description + public static let description = Strings.tr("Localizable", "meetings.scheduleMeeting.waitingRoom.description", fallback: "Only users admitted by the host can join the meeting.") + } + public enum WaitingRoomWarningBanner { + /// Schedule meeting waiting room warning banner learn more + public static let learnMore = Strings.tr("Localizable", "meetings.scheduleMeeting.waitingRoomWarningBanner.learnMore", fallback: "Learn more") + /// Schedule meeting waiting room warning banner title + public static let title = Strings.tr("Localizable", "meetings.scheduleMeeting.waitingRoomWarningBanner.title", fallback: "Participants added by non-hosts during calls won’t be sent to the waiting room.") + } + } + public enum Scheduled { + /// One off meeting date and time description, for a daily recurring meeting without ending date - Wed, 6 Jul 2022 from 09:00 to 10:00 + public static let oneOff = Strings.tr("Localizable", "meetings.scheduled.oneOff", fallback: "[B][WeekDay], [StartDate][/B] from [B][StartTime] to [EndTime][/B]") + public enum ButtonOverlay { + /// Button title to join an scheduled meeting + public static let joinMeeting = Strings.tr("Localizable", "meetings.scheduled.buttonOverlay.joinMeeting", fallback: "Join meeting") + /// Button title to start an scheduled meeting before the start date + public static let startMeeting = Strings.tr("Localizable", "meetings.scheduled.buttonOverlay.startMeeting", fallback: "Start meeting") + } + public enum CancelAlert { + /// Title for alert cancelling an scheduled meeting + public static func title(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.scheduled.cancelAlert.title", String(describing: p1), fallback: "Cancel %@?") + } + public enum Description { + /// Description for alert cancelling an scheduled meeting with messages in the chat room + public static let withMessages = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.description.withMessages", fallback: "This meeting will be placed under Past meetings and all participants will be notified.") + /// Description for alert cancelling an scheduled meeting without messages in the chat room + public static let withoutMessages = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.description.withoutMessages", fallback: "This meeting will be archived and all participants will be notified.") + } + public enum Occurrence { + /// Description for alert cancelling an occurrence + public static let description = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.occurrence.description", fallback: "Only this occurrence will be cancelled. All the other occurrences will go ahead as scheduled.") + /// Success message when cancelling an occurrence + public static func success(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.scheduled.cancelAlert.occurrence.success", String(describing: p1), fallback: "%@ occurrence cancelled") + } + /// Title for alert cancelling an occurrence + public static func title(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.scheduled.cancelAlert.occurrence.title", String(describing: p1), fallback: "Cancel %@ occurrence?") + } + public enum Last { + /// Title for alert cancelling last occurrence + public static let title = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.occurrence.last.title", fallback: "Cancel occurrence and meeting?") + public enum WithMessages { + /// Description for alert cancelling last occurrence with messages in chat room + public static let description = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.occurrence.last.withMessages.description", fallback: "This is the only occurrence of the recurring meeting. Cancelling this occurrence will cancel the meeting. If cancelled, the meeting will be placed under Past meetings and all participants will be notified.") + } + public enum WithoutMessages { + /// Description for alert cancelling last occurrence without messages in chat room + public static let description = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.occurrence.last.withoutMessages.description", fallback: "This is the only occurrence of the recurring meeting. Cancelling this occurrence will cancel the meeting. If cancelled, the meeting will be archived and all participants will be notified.") + } + } + public enum Option { + /// Confirm cancel option for alert cancelling an occurrence + public static let confirm = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.occurrence.option.confirm", fallback: "Cancel occurrence") + } + } + public enum Option { + /// Don't cancel option for alert cancelling an scheduled meeting + public static let dontCancel = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.option.dontCancel", fallback: "Don’t cancel") + public enum Confirm { + /// Confirm cancel option for alert cancelling an scheduled meeting with messages in chat room + public static let withMessages = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.option.confirm.withMessages", fallback: "Cancel meeting") + /// Confirm cancel option for alert cancelling an scheduled meeting without messages in chat room + public static let withoutMessages = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.option.confirm.withoutMessages", fallback: "Cancel and archive") + } + } + public enum Success { + /// Success message when cancelling an scheduled meeting with messages in chat room + public static let withMessages = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.success.withMessages", fallback: "Meeting cancelled") + /// Success message when cancelling an scheduled meeting without messages in chat room + public static let withoutMessages = Strings.tr("Localizable", "meetings.scheduled.cancelAlert.success.withoutMessages", fallback: "Meeting cancelled and archived") + } + } + public enum ContextMenu { + /// Button title to cancel an scheduled meeting from context menu + public static let cancel = Strings.tr("Localizable", "meetings.scheduled.contextMenu.cancel", fallback: "Cancel") + /// Button title to join an scheduled meeting from context menu + public static let joinMeeting = Strings.tr("Localizable", "meetings.scheduled.contextMenu.joinMeeting", fallback: "Join") + /// Button title to show list of occurrences of an scheduled meeting from context menu + public static let occurrences = Strings.tr("Localizable", "meetings.scheduled.contextMenu.occurrences", fallback: "Occurrences") + /// Button title to start an scheduled meeting before the start date from context menu + public static let startMeeting = Strings.tr("Localizable", "meetings.scheduled.contextMenu.startMeeting", fallback: "Start") + } + public enum Create { + public enum Daily { + /// Plural format key: "%#@count@" + public static func footerNote(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.daily.footerNote", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@day@" + public static func interval(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.daily.interval", p1, fallback: "Plural format key: \"%#@day@\"") + } + } + public enum Monthly { + /// Plural format key: "%#@month@" + public static func interval(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.monthly.interval", p1, fallback: "Plural format key: \"%#@month@\"") + } + public enum MultipleDays { + /// Plural format key: "%#@count@" + public static func footerNote(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.monthly.multipleDays.footerNote", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum MultipleDaysCardinal { + /// Plural format key: "%#@count@" + public static func footerNote(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.monthly.multipleDaysCardinal.footerNote", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum SingleDay { + /// Plural format key: "%#@count@" + public static func footerNote(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.monthly.singleDay.footerNote", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum SingleDayCardinal { + /// Plural format key: "%#@count@" + public static func footerNote(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.monthly.singleDayCardinal.footerNote", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum WeekDay { + /// Plural format key: "%#@count@" + public static func selectedFrequency(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.monthly.weekDay.selectedFrequency", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum WeekDayCardinal { + /// Plural format key: "%#@count@" + public static func selectedFrequency(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.monthly.weekDayCardinal.selectedFrequency", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum WeekNumberAndWeekDay { + /// Plural format key: "%#@count@" + public static func footerNote(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.monthly.weekNumberAndWeekDay.footerNote", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func selectedFrequency(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.monthly.weekNumberAndWeekDay.selectedFrequency", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + } + public enum Weekly { + /// Plural format key: "%#@week@" + public static func interval(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.weekly.interval", p1, fallback: "Plural format key: \"%#@week@\"") + } + /// Plural format key: "%#@count@" + public static func selectedFrequency(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.weekly.selectedFrequency", p1, fallback: "Plural format key: \"%#@count@\"") + } + public enum EveryDay { + /// Create Schedule Meeting: Schedule meeting repeats all days in a week. This text is shown in the custom option selection screen + public static let footerNote = Strings.tr("Localizable", "meetings.scheduled.create.weekly.everyDay.footerNote", fallback: "Meeting will occur every day.") + } + public enum MultipleDays { + /// Plural format key: "%#@count@" + public static func footerNote(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.weekly.multipleDays.footerNote", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum SingleDay { + /// Plural format key: "%#@count@" + public static func footerNote(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.create.weekly.singleDay.footerNote", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + } + } + public enum EditMeeting { + /// Edit Schedule meeting navigaion title. + public static let title = Strings.tr("Localizable", "meetings.scheduled.editMeeting.title", fallback: "Edit meeting") + } + public enum Listing { + public enum InProgress { + /// Description shown when a meeting is in progress. This text appears in the chat listing screeen. + public static let description = Strings.tr("Localizable", "meetings.scheduled.listing.inProgress.description", fallback: "Meeting in progress") + /// Description shown when a meeting is in progress. Description includes time. This text appears in the chat listing screeen. + public static func descriptionWithDuration(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.scheduled.listing.inProgress.descriptionWithDuration", String(describing: p1), fallback: "Meeting in progress · %@") + } + } + } + public enum ManagementMessages { + /// A log message in the chat conversation to tell the reader that a participant [A] cancelled the meeting. For example: Zadie Smith cancelled this meeting + public static func cancelled(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.scheduled.managementMessages.cancelled", String(describing: p1), fallback: "%@ cancelled this meeting") + } + /// A log message in the chat conversation to tell the reader that a participant [A] created the meeting. For example: Zadie Smith created this meeting + public static func created(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.scheduled.managementMessages.created", String(describing: p1), fallback: "%@ created this meeting") + } + /// Schedule meeting management message: Description in the chatroom whenever there is an update in the scheduled meeting. + public static func updated(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.scheduled.managementMessages.updated", String(describing: p1), fallback: "%@ updated the meeting") + } + public enum Cancelled { + /// A log message in the chat conversation to tell the reader that a participant [A] cancelled one ocurrence. For example: Zadie Smith cancelled the occurrence scheduled for Fri 22 April from 10:00 to 11:00 + public static func occurrence(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "meetings.scheduled.managementMessages.cancelled.occurrence", String(describing: p1), String(describing: p2), fallback: "%@ cancelled the occurrence scheduled for %@") + } + /// A log message in the chat conversation to tell the reader that a participant [A] cancelled a meeting. For example: Zadie Smith cancelled this meeting + public static func oneOffMeeting(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.scheduled.managementMessages.cancelled.oneOffMeeting", String(describing: p1), fallback: "%@ cancelled this meeting") + } + /// A log message in the chat conversation to tell the reader that a participant [A] cancelled a meeting with recurrence. For example: Zadie Smith cancelled this meeting and all its occurrences + public static func recurrenceMeeting(_ p1: Any) -> String { + return Strings.tr("Localizable", "meetings.scheduled.managementMessages.cancelled.recurrenceMeeting", String(describing: p1), fallback: "%@ cancelled this meeting and all its occurrences") + } + } + } + public enum Recurring { + /// Text description for a meeting showing that is a daily recurring meeting + public static let daily = Strings.tr("Localizable", "meetings.scheduled.recurring.daily", fallback: "daily") + /// Text description for a meeting showing that is a monthly recurring meeting + public static let monthly = Strings.tr("Localizable", "meetings.scheduled.recurring.monthly", fallback: "monthly") + /// Text description for a meeting showing that is a weekly recurring meeting + public static let weekly = Strings.tr("Localizable", "meetings.scheduled.recurring.weekly", fallback: "weekly") + public enum Daily { + /// Plural format key: "%#@interval@" + public static func forever(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.daily.forever", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func until(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.daily.until", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Frequency { + /// Label to indicate the daily recurrence of an scheduled meeting + public static let daily = Strings.tr("Localizable", "meetings.scheduled.recurring.frequency.daily", fallback: "Occurs daily") + /// Label to indicate the monthly recurrence of an scheduled meeting + public static let monthly = Strings.tr("Localizable", "meetings.scheduled.recurring.frequency.monthly", fallback: "Occurs monthly") + /// Label to indicate the weekly recurrence of an scheduled meeting + public static let weekly = Strings.tr("Localizable", "meetings.scheduled.recurring.frequency.weekly", fallback: "Occurs weekly") + } + public enum Monthly { + public enum OrdinalDay { + public enum Forever { + public enum Friday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Monday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Saturday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Sunday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Thursday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Tuesday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Wednesday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + } + public enum Until { + public enum Friday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.friday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.friday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.friday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.friday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.friday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Monday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.monday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.monday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.monday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.monday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.monday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Saturday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Sunday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Thursday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Tuesday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum Wednesday { + /// Plural format key: "%#@interval@" + public static func fifth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.fifth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func first(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.first", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func fourth(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.fourth", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func second(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.second", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func third(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.third", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + } + } + public enum SingleDay { + /// Plural format key: "%#@interval@" + public static func forever(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.singleDay.forever", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func until(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.monthly.singleDay.until", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + } + public enum Occurrences { + public enum List { + /// Button label to indicate that more occurencies can be loaded in the list + public static let seeMoreOccurrences = Strings.tr("Localizable", "meetings.scheduled.recurring.occurrences.list.seeMoreOccurrences", fallback: "See more") + } + } + public enum Weekly { + public enum OneDay { + /// Plural format key: "%#@interval@" + public static func forever(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.weekly.oneDay.forever", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func until(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.weekly.oneDay.until", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + public enum SeveralDays { + /// Plural format key: "%#@interval@" + public static func forever(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.weekly.severalDays.forever", p1, fallback: "Plural format key: \"%#@interval@\"") + } + /// Plural format key: "%#@interval@" + public static func until(_ p1: Int) -> String { + return Strings.tr("Localizable", "meetings.scheduled.recurring.weekly.severalDays.until", p1, fallback: "Plural format key: \"%#@interval@\"") + } + } + } + } + } + public enum Sharelink { + /// Message shown when the meeting link cannot be generated + public static let error = Strings.tr("Localizable", "meetings.sharelink.Error", fallback: "Meeting link could not be generated. Please try again.") + } + public enum StartConversation { + public enum ContextMenu { + /// Meeting - start conversation context menu text for joining a meeting + public static let joinMeeting = Strings.tr("Localizable", "meetings.startConversation.contextMenu.joinMeeting", fallback: "Join meeting") + /// Start conversation: Schedule meeting context menu + public static let scheduleMeeting = Strings.tr("Localizable", "meetings.startConversation.contextMenu.scheduleMeeting", fallback: "Schedule meeting") + /// Meeting - start converstion context menu text for starting a meeting + public static let startMeeting = Strings.tr("Localizable", "meetings.startConversation.contextMenu.startMeeting", fallback: "Start meeting now") + } + } + public enum WaitingRoom { + /// Meeting waiting room leave button + public static let leave = Strings.tr("Localizable", "meetings.waitingRoom.leave", fallback: "Leave") + public enum Alert { + /// Meeting waiting room don't leave button + public static let dontLeave = Strings.tr("Localizable", "meetings.waitingRoom.alert.dontLeave", fallback: "Don’t leave") + /// Meeting waiting room alert title of Host didn’t let you in + public static let hostDidNotLetYouIn = Strings.tr("Localizable", "meetings.waitingRoom.alert.hostDidNotLetYouIn", fallback: "Host didn’t let you in") + /// Meeting waiting room leave alert message + public static let leaveMeeting = Strings.tr("Localizable", "meetings.waitingRoom.alert.leaveMeeting", fallback: "Leave meeting?") + /// Meeting waiting room alert action of ok got it + public static let okGotIt = Strings.tr("Localizable", "meetings.waitingRoom.alert.okGotIt", fallback: "OK, got it") + /// Meeting waiting room alert message of You'll be removed from the waiting room + public static let youWillBeRemovedFromTheWaitingRoom = Strings.tr("Localizable", "meetings.waitingRoom.alert.youWillBeRemovedFromTheWaitingRoom", fallback: "You’ll be removed from the waiting room") + } + public enum Guest { + /// Meeting waiting room guest join first name textfield + public static let firstName = Strings.tr("Localizable", "meetings.waitingRoom.guest.firstName", fallback: "First name") + /// Meeting waiting room guest join button + public static let join = Strings.tr("Localizable", "meetings.waitingRoom.guest.join", fallback: "Join") + /// Meeting waiting room guest join last name textfield + public static let lastName = Strings.tr("Localizable", "meetings.waitingRoom.guest.lastName", fallback: "Last name") + } + public enum Message { + /// Meeting waiting room message of wait for host to let you in + public static let waitForHostToLetYouIn = Strings.tr("Localizable", "meetings.waitingRoom.message.waitForHostToLetYouIn", fallback: "Wait for host to let you in") + /// Meeting waiting room message of wait for host to start the meeting + public static let waitForHostToStartTheMeeting = Strings.tr("Localizable", "meetings.waitingRoom.message.waitForHostToStartTheMeeting", fallback: "Wait for host to start the meeting") + } + } + } + public enum Mybackups { + public enum Share { + public enum Folder { + public enum Warning { + /// Plural format key: "%#@share@" + public static func message(_ p1: Int) -> String { + return Strings.tr("Localizable", "mybackups.share.folder.warning.message", p1, fallback: "Plural format key: \"%#@share@\"") + } + } + } + } + } + public enum NameCollision { + /// Plural format key: "%#@count@" + public static func applyToAll(_ p1: Int) -> String { + return Strings.tr("Localizable", "nameCollision.applyToAll", p1, fallback: "Plural format key: \"%#@count@\"") + } + public enum Files { + /// Text to indicate the user that the file trying to upload/copy/move already exists in destination + public static func alreadyExists(_ p1: Any) -> String { + return Strings.tr("Localizable", "nameCollision.files.alreadyExists", String(describing: p1), fallback: "A file named %@ already exists at this destination.") + } + public enum Action { + public enum Rename { + /// Text description for the rename action when upload/copy/move and already exists in destination + public static let description = Strings.tr("Localizable", "nameCollision.files.action.rename.description", fallback: "The file will be renamed as:") + /// Text of the rename action when trying to upload/copy/move a file and already exists in destination + public static let title = Strings.tr("Localizable", "nameCollision.files.action.rename.title", fallback: "Rename") + } + public enum Replace { + /// Text description for the replace action when upload/copy/move and already exists in destination + public static let description = Strings.tr("Localizable", "nameCollision.files.action.replace.description", fallback: "The file at this destination will be replaced with the new file.") + /// Text of the replace action trying to upload/copy/move a file and already exists in destination + public static let title = Strings.tr("Localizable", "nameCollision.files.action.replace.title", fallback: "Replace") + } + public enum Skip { + /// Text of the skip action when trying to upload/copy/move a file and already exists in destination + public static let title = Strings.tr("Localizable", "nameCollision.files.action.skip.title", fallback: "Skip this file") + } + public enum Update { + /// Text description for the update action when upload and already exists in destination + public static let description = Strings.tr("Localizable", "nameCollision.files.action.update.description", fallback: "The file will be updated with a new version.") + /// Text of the update action when trying to upload a file and already exists in destination + public static let title = Strings.tr("Localizable", "nameCollision.files.action.update.title", fallback: "Update") + } + } + } + public enum Folders { + /// Text to indicate the user that the folder trying to upload/copy/move already exists in destination + public static func alreadyExists(_ p1: Any) -> String { + return Strings.tr("Localizable", "nameCollision.folders.alreadyExists", String(describing: p1), fallback: "A folder named %@ already exists at this destination.") + } + public enum Action { + public enum Merge { + /// Text description for the merge action when upload/copy/move and already exists in destination + public static let description = Strings.tr("Localizable", "nameCollision.folders.action.merge.description", fallback: "The new folder will be merged with the folder at this destination.") + /// Text of the merge action when trying to upload/copy/move a folder and already exists in destination + public static let title = Strings.tr("Localizable", "nameCollision.folders.action.merge.title", fallback: "Merge") + } + public enum Skip { + /// Text of the skip action when trying to upload/copy/move a folder and already exists in destination + public static let title = Strings.tr("Localizable", "nameCollision.folders.action.skip.title", fallback: "Skip this folder") + } + } + } + public enum Title { + /// Text to indicate the user that file already exists when uploading/copying/moving + public static let file = Strings.tr("Localizable", "nameCollision.title.file", fallback: "File already exists") + /// Text to indicate the user that folder already exists when uploading/copying/moving + public static let folder = Strings.tr("Localizable", "nameCollision.title.folder", fallback: "Folder already exists") + } + } + public enum Notifications { + public enum Message { + public enum TakenDownPubliclyShared { + /// The text of a notification indicating that a file has been taken down due to infringement or other reason. The %@ will be replaced with the name of the file. + public static func file(_ p1: Any) -> String { + return Strings.tr("Localizable", "notifications.message.takenDownPubliclyShared.file", String(describing: p1), fallback: "Your publicly shared file “%@” has been taken down") + } + /// The text of a notification indicating that a folder has been taken down due to infringement or other reason. The %@ will be replaced with the name of the folder. + public static func folder(_ p1: Any) -> String { + return Strings.tr("Localizable", "notifications.message.takenDownPubliclyShared.folder", String(describing: p1), fallback: "Your publicly shared folder “%@” has been taken down") + } + } + public enum TakenDownReinstated { + /// The text of a notification indicating that a file that was taken down has now been restored due to a successful counter-notice.The %@ will be replaced with the name of the file. + public static func file(_ p1: Any) -> String { + return Strings.tr("Localizable", "notifications.message.takenDownReinstated.file", String(describing: p1), fallback: "Your taken-down file “%@” has been reinstated") + } + /// The text of a notification indicating that a folder that was taken down has now been restored due to a successful counter-notice.The %@ will be replaced with the name of the folder. + public static func folder(_ p1: Any) -> String { + return Strings.tr("Localizable", "notifications.message.takenDownReinstated.folder", String(describing: p1), fallback: "Your taken-down folder “%@” has been reinstated") + } + } + } + } + public enum Offline { + public enum LogOut { + public enum Warning { + /// Offline log out warning message + public static let message = Strings.tr("Localizable", "offline.logOut.warning.message", fallback: "Logging out deletes your offline content.") + } + } + } + public enum Photo { + public enum Empty { + /// Photo view is empty + public static let title = Strings.tr("Localizable", "photo.empty.title", fallback: "No photos found") + } + public enum Navigation { + /// Navigation title for Photo Controller + public static let title = Strings.tr("Localizable", "photo.navigation.title", fallback: "Photos") + } + } + public enum Picker { + public enum Disable { + public enum Passcode { + /// Description shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled, it shows the users how to disable the passcode + public static let description = Strings.tr("Localizable", "picker.disable.passcode.description", fallback: "To use MEGA in the Files app, disable your MEGA passcode. Go to Settings in the MEGA app, tap Security > Passcode.") + /// Title shown in Files app when users open MEGA and passcode is enabled. Files app can't be used with passcode enabled + public static let title = Strings.tr("Localizable", "picker.disable.passcode.title", fallback: "Disable MEGA passcode") + } + } + } + public enum Recents { + public enum EmptyState { + public enum ActivityHidden { + /// Title of the button show in Recents on the empty state when the recent activity is hidden + public static let button = Strings.tr("Localizable", "recents.emptyState.activityHidden.button", fallback: "Show activity") + /// Title show in Recents on the empty state when the recent activity is hidden + public static let title = Strings.tr("Localizable", "recents.emptyState.activityHidden.title", fallback: "Recent activity hidden") + } + } + public enum Section { + public enum MultipleFile { + /// Plural format key: "%#@file@" + public static func title(_ p1: Int) -> String { + return Strings.tr("Localizable", "recents.section.multipleFile.title", p1, fallback: "Plural format key: \"%#@file@\"") + } + } + public enum Thumbnail { + public enum Count { + /// Plural format key: "%#@image@" + public static func image(_ p1: Int) -> String { + return Strings.tr("Localizable", "recents.section.thumbnail.count.image", p1, fallback: "Plural format key: \"%#@image@\"") + } + /// Plural format key: "%#@video@" + public static func video(_ p1: Int) -> String { + return Strings.tr("Localizable", "recents.section.thumbnail.count.video", p1, fallback: "Plural format key: \"%#@video@\"") + } + public enum ImageAndVideo { + /// Plural format key: "%#@image@" + public static func image(_ p1: Int) -> String { + return Strings.tr("Localizable", "recents.section.thumbnail.count.imageAndVideo.image", p1, fallback: "Plural format key: \"%#@image@\"") + } + /// Plural format key: "%#@video@" + public static func video(_ p1: Int) -> String { + return Strings.tr("Localizable", "recents.section.thumbnail.count.imageAndVideo.video", p1, fallback: "Plural format key: \"%#@video@\"") + } + } + } + } + public enum Title { + /// Plural format key: "%#@items@" + public static func items(_ p1: Int) -> String { + return Strings.tr("Localizable", "recents.section.title.items", p1, fallback: "Plural format key: \"%#@items@\"") + } + } + } + } + public enum Rename { + /// Rename file no extension warning text + public static func fileWithoutExtension(_ p1: Any) -> String { + return Strings.tr("Localizable", "rename.fileWithoutExtension", String(describing: p1), fallback: "File without extension .%@") + } + public enum ConfirmationAlert { + /// Rename file confirmation alert description to ask if user is sure to change the file extension. + public static let description = Strings.tr("Localizable", "rename.confirmationAlert.description", fallback: "You might not be able to open this file if you change its extension.") + /// Rename file confirmation confirm button title + public static let ok = Strings.tr("Localizable", "rename.confirmationAlert.ok", fallback: "Change anyway") + /// Rename file confirmation alert title to ask if user is sure to change the file extension. + public static let title = Strings.tr("Localizable", "rename.confirmationAlert.title", fallback: "File extension change") + } + } + public enum Settings { + public enum About { + public enum Sfu { + public enum ChangeAlert { + /// Change SFU server alert cancel button. + public static let cancelButton = Strings.tr("Localizable", "settings.about.sfu.changeAlert.cancelButton", fallback: "Cancel") + /// Change SFU server alert change button. + public static let changeButton = Strings.tr("Localizable", "settings.about.sfu.changeAlert.changeButton", fallback: "Change") + /// Change SFU server alert message. + public static let message = Strings.tr("Localizable", "settings.about.sfu.changeAlert.message", fallback: "Default is -1") + /// Change SFU server alert test field placeholder. + public static let placeholder = Strings.tr("Localizable", "settings.about.sfu.changeAlert.placeholder", fallback: "Enter SFU ID") + /// Change SFU server alert title. + public static let title = Strings.tr("Localizable", "settings.about.sfu.changeAlert.title", fallback: "Change SFU server") + } + } + } + public enum Accept { + public enum Cookies { + /// Cookie dialog footer description + public static let footer = Strings.tr("Localizable", "settings.accept.cookies.footer", fallback: "Cookies aren’t used for ad tracking or sharing any personal information with third parties.") + } + } + public enum Cookies { + /// Cookie settings dialog text. + public static let essential = Strings.tr("Localizable", "settings.cookies.essential", fallback: "Essential Cookies") + /// Cookie settings dialog text. + public static let performanceAndAnalytics = Strings.tr("Localizable", "settings.cookies.performanceAndAnalytics", fallback: "Performance and analytics Cookies") + public enum Essential { + /// Text shown next to Essential Cookies in Cookie Settings. This setting can not be disabled, that is why is 'Always on' + public static let alwaysOn = Strings.tr("Localizable", "settings.cookies.essential.alwaysOn", fallback: "Always On") + /// Cookie settings dialog text. + public static let footer = Strings.tr("Localizable", "settings.cookies.essential.footer", fallback: "Essential for providing you important functionality and secure access to our services. For this reason, they do not require consent.") + } + public enum PerformanceAndAnalytics { + /// Cookie settings dialog text. + public static let footer = Strings.tr("Localizable", "settings.cookies.performanceAndAnalytics.footer", fallback: "Help us to understand how you use our services and provide us data that we can use to make improvements. Not accepting these Cookies will mean we will have less data available to us to help design improvements.") + } + } + public enum FileManagement { + public enum Alert { + /// Question shown after you tap on 'Settings' - 'File Management' - 'Clear Offline files' to confirm the action + public static let clearAllOfflineFiles = Strings.tr("Localizable", "settings.fileManagement.alert.clearAllOfflineFiles", fallback: "Clear all offline files?") + } + public enum RubbishBin { + /// This text is displayed in Settings, File Management in Rubbish Bien view. Upgrade to Pro will be bold and green. And if you tap it, the Upgrade Account view will appear. + public static let longerRetentionUpgrade = Strings.tr("Localizable", "settings.fileManagement.rubbishBin.longerRetentionUpgrade", fallback: "For a longer retention period [S]Upgrade to Pro[/S]") + public enum CleanScheduler { + public enum Placeholder { + /// Textfield placeholder for rubbish bin Cleaning Scheduler in days alert + public static let days = Strings.tr("Localizable", "settings.fileManagement.rubbishBin.cleanScheduler.placeholder.days", fallback: "days") + } + } + } + public enum UseMobileData { + /// Footer explaning how Use Mobile Data setting to load preview of images in hight resolution works + public static let footer = Strings.tr("Localizable", "settings.fileManagement.useMobileData.footer", fallback: "Use mobile data to load high resolution images when previewing. If disabled, the high resolution image will only be loaded when you zoom in.") + /// Header of a Use Mobile Data setting to load preview of images in hight resolution + public static let header = Strings.tr("Localizable", "settings.fileManagement.useMobileData.header", fallback: "Preview high resolution images") + } + } + public enum Section { + /// Title of the Settings section where you can configure security details of your MEGA account + public static let security = Strings.tr("Localizable", "settings.section.security", fallback: "Security") + /// Title of one of the Settings sections where you can see MEGA's 'Terms and Policies' + public static let termsAndPolicies = Strings.tr("Localizable", "settings.section.termsAndPolicies", fallback: "Terms and Policies") + /// Title of one of the Settings sections where you can customise the 'User Interface' of the app. + public static let userInterface = Strings.tr("Localizable", "settings.section.userInterface", fallback: "User interface") + public enum Calls { + /// Title of the Settings section where you can configure calls options of your MEGA account + public static let title = Strings.tr("Localizable", "settings.section.calls.title", fallback: "Calls") + public enum SoundNotifications { + /// Description of the sound notifications choice for calls when users join or left a call + public static let description = Strings.tr("Localizable", "settings.section.calls.soundNotifications.description", fallback: "Hear a sound when someone joins or leaves a call.") + /// Title of the sound notifications choice for calls when users join or left a call + public static let title = Strings.tr("Localizable", "settings.section.calls.soundNotifications.title", fallback: "Sound notifications") + } + } + } + public enum UserInterface { + /// In Settings - User Interface, there is an option that you can enable to hide the contents of the Recents section + public static let hideRecentActivity = Strings.tr("Localizable", "settings.userInterface.hideRecentActivity", fallback: "Hide recent activity") + public enum HideRecentActivity { + /// In Settings - User Interface, there is an option that you can enable to hide the contents of the Recents section. This is the footer that appears under that option. + public static let footer = Strings.tr("Localizable", "settings.userInterface.hideRecentActivity.footer", fallback: "Hide recent activity in Home section.") + } + } + } + public enum Share { + public enum Message { + /// Plural format key: "%#@count@" + public static func uploadedToCloudDrive(_ p1: Int) -> String { + return Strings.tr("Localizable", "share.message.uploadedToCloudDrive", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func uploadedToDestinationFolder(_ p1: Int) -> String { + return Strings.tr("Localizable", "share.message.uploadedToDestinationFolder", p1, fallback: "Plural format key: \"%#@count@\"") + } + public enum SendToChat { + /// Plural format key: "%#@receiverCount@" + public static func withMultipleFiles(_ p1: Int) -> String { + return Strings.tr("Localizable", "share.message.sendToChat.withMultipleFiles", p1, fallback: "Plural format key: \"%#@receiverCount@\"") + } + /// Plural format key: "%#@receiverCount@" + public static func withOneFile(_ p1: Int) -> String { + return Strings.tr("Localizable", "share.message.sendToChat.withOneFile", p1, fallback: "Plural format key: \"%#@receiverCount@\"") + } + } + } + } + public enum ShareFolder { + /// Text displayed on banner we show when sharing folder with unverified contacts + public static let contactsNotVerified = Strings.tr("Localizable", "shareFolder.contactsNotVerified", fallback: "Some of the contacts you’re sharing information with haven’t been approved by you. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve.") + } + public enum SharedItems { + public enum ContactVerification { + /// On Cloud Drive screen for incoming shared folder, if the contact which shared the folder is not verified, this is the message that wwe display + public static func contactNotVerifiedBannerMessage(_ p1: Any) -> String { + return Strings.tr("Localizable", "sharedItems.contactVerification.contactNotVerifiedBannerMessage", String(describing: p1), fallback: "%@ is shared by a contact you haven’t approved. To ensure extra security, we recommend that you approve their credentials in Contacts by tapping on ⓘ next to the contact you want to approve.") + } + public enum Section { + public enum MyCredentials { + /// On fingerprint verification screen for Shared Items. Header message located at the top of "My credentials" section + public static let message = Strings.tr("Localizable", "sharedItems.contactVerification.section.myCredentials.message", fallback: "To approve your contact, ensure the credentials you see above match their account credentials. You can ask them to share their credentials with you.") + } + public enum VerifyContact { + /// On fingerprint verification screen for Incoming Shared Items. Yellow banner message that will be shown to the receiver if the owner of the folder haven't verified them yet. + public static let bannerMessage = Strings.tr("Localizable", "sharedItems.contactVerification.section.verifyContact.bannerMessage", fallback: "To access the shared folder, the person who shared it with you should approve you, too.") + public enum Owner { + /// On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address that needs to be verified + public static let message = Strings.tr("Localizable", "sharedItems.contactVerification.section.verifyContact.owner.message", fallback: "We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you share information with before they can access the shared folders.") + } + public enum Receiver { + /// On fingerprint verification screen for Shared Items. Header message located at the top of contact's name and email address if the owner of the received shared item is unverified + public static let message = Strings.tr("Localizable", "sharedItems.contactVerification.section.verifyContact.receiver.message", fallback: "We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to approve the contacts you receive information from before you can access the shared folders.") + } + } + } + } + public enum GetLink { + /// Plural format key: "%#@count@" + public static func linkCopied(_ p1: Int) -> String { + return Strings.tr("Localizable", "sharedItems.getLink.linkCopied", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func linkCreatedAndCopied(_ p1: Int) -> String { + return Strings.tr("Localizable", "sharedItems.getLink.linkCreatedAndCopied", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum Link { + /// Plural format key: "%#@count@" + public static func accessInfo(_ p1: Int) -> String { + return Strings.tr("Localizable", "sharedItems.link.accessInfo", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// This is a description of a row on Get Link dialog which explains who will be able to access the link if the password is added to link + public static let accessInfoPasswordProtected = Strings.tr("Localizable", "sharedItems.link.accessInfoPasswordProtected", fallback: "Only people with the password can open the link") + /// This is a text of a banner which will be displayed user does an action which updates the shared link + public static let linkUpdated = Strings.tr("Localizable", "sharedItems.link.linkUpdated", fallback: "Link updated. Copy again.") + } + public enum Menu { + public enum Slideshow { + /// Slideshow Menu Option + public static let title = Strings.tr("Localizable", "sharedItems.menu.slideshow.title", fallback: "Slideshow") + } + } + public enum Rubbish { + public enum Confirmation { + /// Plural format key: "%#@count@" + public static func fileCount(_ p1: Int) -> String { + return Strings.tr("Localizable", "sharedItems.rubbish.confirmation.fileCount", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func folderCount(_ p1: Int) -> String { + return Strings.tr("Localizable", "sharedItems.rubbish.confirmation.folderCount", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Success message shown when [A] = {1+} files and [B] = "{1+} file{s} {and} {1+} folder{s} have been removed from MEGA" + public static func message(_ p1: Any) -> String { + return Strings.tr("Localizable", "sharedItems.rubbish.confirmation.message", String(describing: p1), fallback: "%@ removed from MEGA") + } + /// Plural format key: "%#@count@" + public static func removedItemCount(_ p1: Int) -> String { + return Strings.tr("Localizable", "sharedItems.rubbish.confirmation.removedItemCount", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + public enum Warning { + /// Plural format key: "%#@count@" + public static func fileCount(_ p1: Int) -> String { + return Strings.tr("Localizable", "sharedItems.rubbish.warning.fileCount", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Plural format key: "%#@count@" + public static func folderCount(_ p1: Int) -> String { + return Strings.tr("Localizable", "sharedItems.rubbish.warning.folderCount", p1, fallback: "Plural format key: \"%#@count@\"") + } + /// Alert message shown on the Rubbish Bin when you want to remove "{1+} file{s} {and} {1+} folder{s}" + public static func message(_ p1: Any) -> String { + return Strings.tr("Localizable", "sharedItems.rubbish.warning.message", String(describing: p1), fallback: "You are about to permanently remove %@. Would you like to proceed? (You cannot undo this action.)") + } + } + } + public enum Tab { + public enum Incoming { + /// On Incoming Shared Items Tab. Name of the folder if the the owner and the receiver has not yet verified each other. + public static let undecryptedFolderName = Strings.tr("Localizable", "sharedItems.tab.incoming.undecryptedFolderName", fallback: "[Undecrypted folder]") + } + public enum Outgoing { + /// On Outgoing Shared Items Tab. Text that shows the receiver's name of the shared folder. %@ will be replaced with the receiver's name. + public static func sharedToContact(_ p1: Any) -> String { + return Strings.tr("Localizable", "sharedItems.tab.outgoing.sharedToContact", String(describing: p1), fallback: "Shared with %@") + } + public enum Modal { + public enum CannotVerifyContact { + /// On Outgoing Shared Items Tab. Message of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. + public static func message(_ p1: Any) -> String { + return Strings.tr("Localizable", "sharedItems.tab.outgoing.modal.cannotVerifyContact.message", String(describing: p1), fallback: "You can’t approve %@ as they’re not in your contact list. Wait for them to accept your invitation first.") + } + /// On Outgoing Shared Items Tab. Title of the modal shown when trying to verify pending outshare where receiver is not in contacts yet. + public static let title = Strings.tr("Localizable", "sharedItems.tab.outgoing.modal.cannotVerifyContact.title", fallback: "Cannot approve contact") + } + } + } + public enum Recents { + /// Plural format key: "%#@count@" + public static func undecryptedFileName(_ p1: Int) -> String { + return Strings.tr("Localizable", "sharedItems.tab.recents.undecryptedFileName", p1, fallback: "Plural format key: \"%#@count@\"") + } + } + } + } + public enum Slideshow { + public enum PreferenceSetting { + /// Footer note for media in sub folders + public static let mediaInSubFolders = Strings.tr("Localizable", "slideshow.preferenceSetting.mediaInSubFolders", fallback: "Include images from sub-folders in the slideshow") + /// Option button Title shown on Slideshow Preference Setting + public static let options = Strings.tr("Localizable", "slideshow.preferenceSetting.options", fallback: "Options") + /// NavigationBar Title shown on Slideshow Order preference + public static let order = Strings.tr("Localizable", "slideshow.preferenceSetting.order", fallback: "Slideshow order") + /// NavigationBar Title shown on Slideshow Preference Setting + public static let slideshowOptions = Strings.tr("Localizable", "slideshow.preferenceSetting.slideshowOptions", fallback: "Slideshow options") + /// NavigationBar Title shown on Slideshow Speed preference + public static let speed = Strings.tr("Localizable", "slideshow.preferenceSetting.speed", fallback: "Slideshow speed") + public enum Order { + /// Slideshow Speed Order shuffle + public static let shuffle = Strings.tr("Localizable", "slideshow.preferenceSetting.order.shuffle", fallback: "Shuffle") + } + public enum SlideshowOptions { + /// Slideshow Preference Setting repeat + public static let `repeat` = Strings.tr("Localizable", "slideshow.preferenceSetting.slideshowOptions.repeat", fallback: "Repeat") + /// Slideshow Preference Setting include subFolders + public static let subFolders = Strings.tr("Localizable", "slideshow.preferenceSetting.slideshowOptions.subFolders", fallback: "Include sub-folders") + } + public enum Speed { + /// Slideshow Speed preference fast + public static let fast = Strings.tr("Localizable", "slideshow.preferenceSetting.speed.fast", fallback: "Fast (2s)") + /// Slideshow Speed preference normal + public static let normal = Strings.tr("Localizable", "slideshow.preferenceSetting.speed.normal", fallback: "Normal (4s)") + /// Slideshow Speed preference slow + public static let slow = Strings.tr("Localizable", "slideshow.preferenceSetting.speed.slow", fallback: "Slow (8s)") + } + } + } + public enum Transfer { + public enum Cell { + public enum ShareOwnerStorageQuota { + /// A message shown when uploading to an incoming share and the owner’s account is over its storage quota. + public static let infoLabel = Strings.tr("Localizable", "transfer.cell.shareOwnerStorageQuota.infoLabel", fallback: "Share owner is over storage quota.") + } + } + public enum Error { + /// Error shown when downloading a file that has violated Terms of Service. + public static let termsOfServiceViolation = Strings.tr("Localizable", "transfer.error.termsOfServiceViolation", fallback: "Violated Terms of Service") + } + public enum Storage { + /// Label indicating storage over quota + public static let quotaExceeded = Strings.tr("Localizable", "transfer.storage.quotaExceeded", fallback: "Storage quota exceeded") + } + } + public enum TransferQuotaError { + public enum Button { + /// Button title of the dialog for transfer quota error to buy new plan + public static let buyNewPlan = Strings.tr("Localizable", "transferQuotaError.button.buyNewPlan", fallback: "Buy new plan") + /// Button title of the dialog for transfer quota error that will direct to upgrade plan + public static let upgrade = Strings.tr("Localizable", "transferQuotaError.button.upgrade", fallback: "Upgrade") + /// Button title of the dialog for transfer quota error that will dismiss dialog + public static let wait = Strings.tr("Localizable", "transferQuotaError.button.wait", fallback: "I will wait") + } + public enum DownloadExceededQuota { + /// Title of the dialog for transfer quota error with exceeded quota + public static let title = Strings.tr("Localizable", "transferQuotaError.downloadExceededQuota.title", fallback: "Transfer quota exceeded") + public enum FreeAccount { + /// Message text of the dialog for transfer quota error with exceeded download trasfer quota for free accounts + public static func message(_ p1: Any) -> String { + return Strings.tr("Localizable", "transferQuotaError.downloadExceededQuota.freeAccount.message", String(describing: p1), fallback: "You can’t continue downloading as you don’t have any transfer quota left for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota.") + } + } + public enum ProAccount { + /// Message text of the dialog for transfer quota error with exceeded download trasfer quota for pro accounts + public static let message = Strings.tr("Localizable", "transferQuotaError.downloadExceededQuota.proAccount.message", fallback: "You can’t continue downloading as you don’t have any transfer quota left on this account. To get more quota, purchase a new plan, or if you have a recurring subscription with MEGA, you can wait for your plan to renew.") + } + } + public enum DownloadLimitedQuota { + /// Title of the dialog for transfer quota error with limited available trasfer quota + public static let title = Strings.tr("Localizable", "transferQuotaError.downloadLimitedQuota.title", fallback: "Limited available transfer quota") + public enum FreeAccount { + /// Message text of the dialog for transfer quota error with limited available trasfer quota for free accounts + public static func message(_ p1: Any) -> String { + return Strings.tr("Localizable", "transferQuotaError.downloadLimitedQuota.freeAccount.message", String(describing: p1), fallback: "Downloading may be interrupted as you’ve used most of your transfer quota for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota.") + } + } + public enum ProAccount { + /// Message text of the dialog for transfer quota error with limited available trasfer quota for pro accounts + public static let message = Strings.tr("Localizable", "transferQuotaError.downloadLimitedQuota.proAccount.message", fallback: "Downloading may be interrupted as you’ve used most of your transfer quota on this account. To get more quota, purchase a new plan, or if you have a recurring subscription with MEGA, you can wait for your plan to renew.") + } + } + public enum FooterMessage { + /// Footer message of the dialog for transfer quota error. [A] will be replaced by the usage quota percent. [B] will be replaced by the max transfer quota in TB. For example: 100% of 16 TB used + public static let quotaUsage = Strings.tr("Localizable", "transferQuotaError.footerMessage.quotaUsage", fallback: "[A]%% of [B] TB used") + } + public enum StreamingExceededQuota { + public enum FreeAccount { + /// Message text of the dialog for transfer quota error with exceeded streaming trasfer quota for free accounts + public static func message(_ p1: Any) -> String { + return Strings.tr("Localizable", "transferQuotaError.streamingExceededQuota.freeAccount.message", String(describing: p1), fallback: "Your media stopped playing as you don’t have any transfer quota left for this IP address. To get more quota, upgrade to a Pro plan or wait for %@ until more free quota becomes available on your IP address. [A]Learn more[/A] about transfer quota.") + } + } + public enum ProAccount { + /// Message text of the dialog for transfer quota error with exceeded download trasfer quota for pro accounts + public static let message = Strings.tr("Localizable", "transferQuotaError.streamingExceededQuota.proAccount.message", fallback: "Your media stopped playing as you don’t have any transfer quota left on this account. To get more quota, purchase a new plan, or if you have a recurring subscription with MEGA, you can wait for your plan to renew.") + } + } + } + public enum Transfers { + public enum Cancellable { + /// Message for action of cancelling a transfer + public static let cancel = Strings.tr("Localizable", "transfers.cancellable.cancel", fallback: "Cancel transfer") + /// Cancelling transfers step title message for cancellable transfer + public static let cancellingTransfers = Strings.tr("Localizable", "transfers.cancellable.cancellingTransfers", fallback: "Cancelling transfers…") + /// Message informing the user about transfers lost + public static let confirmCancel = Strings.tr("Localizable", "transfers.cancellable.confirmCancel", fallback: "Interrupting the transfer process may render some of the items incomplete.") + /// Second step title message for cancellable transfer + public static let creatingFolders = Strings.tr("Localizable", "transfers.cancellable.creatingFolders", fallback: "Creating folders…") + /// Dismiss action of cancelling a transfer + public static let dismiss = Strings.tr("Localizable", "transfers.cancellable.dismiss", fallback: "No, continue") + /// Message asking the user to not close the app during a process + public static let donotclose = Strings.tr("Localizable", "transfers.cancellable.donotclose", fallback: "Don’t close the app. If you close, transfers not yet queued will be lost.") + /// Confirm action of cancelling a transfer + public static let proceed = Strings.tr("Localizable", "transfers.cancellable.proceed", fallback: "Yes, cancel") + /// First step title message for cancellable transfer + public static let scanning = Strings.tr("Localizable", "transfers.cancellable.scanning", fallback: "Scanning…") + /// Title of the alert shown when you confirm to cancel pending transfers + public static let title = Strings.tr("Localizable", "transfers.cancellable.title", fallback: "Cancel transfers?") + /// Third step title message for cancellable transfer + public static let transferring = Strings.tr("Localizable", "transfers.cancellable.transferring", fallback: "Transferring…") + /// Message info for a cancelled transfer + public static let trasnferCancelled = Strings.tr("Localizable", "transfers.cancellable.trasnferCancelled", fallback: "Transfer cancelled") + public enum CreatingFolders { + /// Count of folders created in the create folders stage + public static func count(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "transfers.cancellable.creatingFolders.count", String(describing: p1), String(describing: p2), fallback: "%@/%@") + } + } + public enum Scanning { + /// Count of folders and files in the scanning stage + public static func count(_ p1: Any, _ p2: Any) -> String { + return Strings.tr("Localizable", "transfers.cancellable.scanning.count", String(describing: p1), String(describing: p2), fallback: "Found %@ and %@") + } + } + } + } + public enum UpgradeAccountPlan { + public enum Button { + public enum BuyAccountPlan { + /// On Upgrade Account Plan screen. Button title to buy account plan. %@ will be the name of the selected plan + public static func title(_ p1: Any) -> String { + return Strings.tr("Localizable", "upgradeAccountPlan.button.buyAccountPlan.title", String(describing: p1), fallback: "Buy %@") + } + } + public enum Restore { + /// On Upgrade Account Plan screen. Button title to restore purchases + public static let title = Strings.tr("Localizable", "upgradeAccountPlan.button.restore.title", fallback: "Restore purchase") + } + public enum TermsAndPolicies { + /// On Upgrade Account Plan screen. Button title to show terms and policies + public static let title = Strings.tr("Localizable", "upgradeAccountPlan.button.termsAndPolicies.title", fallback: "Terms and policies") + } + } + public enum Footer { + public enum Message { + /// On Upgrade Account Plan screen. Footer message with pricing page link. + public static let pricingPage = Strings.tr("Localizable", "upgradeAccountPlan.footer.message.pricingPage", fallback: "You can upgrade your current subscription on our [A]pricing page[/A].") + } + } + public enum Header { + public enum PlanTermPicker { + /// On Upgrade Account Plan screen. Account Plan picker option for monthly term. + public static let monthly = Strings.tr("Localizable", "upgradeAccountPlan.header.planTermPicker.monthly", fallback: "Monthly") + /// On Upgrade Account Plan screen. Account Plan picker option for yearly term. + public static let yearly = Strings.tr("Localizable", "upgradeAccountPlan.header.planTermPicker.yearly", fallback: "Yearly") + } + public enum Title { + /// On Upgrade Account Plan screen. Displayed header title text message + public static let choosePlan = Strings.tr("Localizable", "upgradeAccountPlan.header.title.choosePlan", fallback: "Choose the right plan for you") + /// On Upgrade Account Plan screen. Displayed header title text with current plan. %@ will be the name of the current plan + public static func currentPlan(_ p1: Any) -> String { + return Strings.tr("Localizable", "upgradeAccountPlan.header.title.currentPlan", String(describing: p1), fallback: "Current plan: %@") + } + /// On Upgrade Account Plan screen. Displayed header title text for pro plan features + public static let featuresOfProPlan = Strings.tr("Localizable", "upgradeAccountPlan.header.title.featuresOfProPlan", fallback: "Features of Pro plans") + /// On Upgrade Account Plan screen. Displayed header title text with percent of savings with yearly billing. + public static let saveYearlyBilling = Strings.tr("Localizable", "upgradeAccountPlan.header.title.saveYearlyBilling", fallback: "Save up to 16%% with yearly billing") + /// On Upgrade Account Plan screen. Displayed header title text for subscription details + public static let subscriptionDetails = Strings.tr("Localizable", "upgradeAccountPlan.header.title.subscriptionDetails", fallback: "Subscription details") + } + } + public enum Message { + public enum Text { + /// On Upgrade Account Plan screen. Displayed text body for pro plan features + public static let featuresOfProPlan = Strings.tr("Localizable", "upgradeAccountPlan.message.text.featuresOfProPlan", fallback: "• Password-protected links\n\n• Links with expiry dates\n\n• Transfer quota sharing\n\n• Automatic backups\n\n• Rewind up to 90 days on Pro Lite and up to 365 days on Pro I, II, and III (coming soon)\n\n• Schedule rubbish bin clearing between 7 days and 10 years") + /// On Upgrade Account Plan screen. Displayed text body for subscription details + public static let subscriptionDetails = Strings.tr("Localizable", "upgradeAccountPlan.message.text.subscriptionDetails", fallback: "Subscriptions are renewed automatically for successive subscription periods of the same duration and at the same price as the initial period chosen.\nYou can switch off the automatic renewal of your MEGA Pro subscription no later than 24 hours before your next subscription payment is due via your iTunes account settings page.") + } + } + public enum Plan { + public enum Details { + /// On Upgrade Account Plan screen. Displayed Storage detail on account plan options + public static func storage(_ p1: Any) -> String { + return Strings.tr("Localizable", "upgradeAccountPlan.plan.details.storage", String(describing: p1), fallback: "Storage: %@") + } + /// On Upgrade Account Plan screen. Displayed Transfer detail on account plan options + public static func transfer(_ p1: Any) -> String { + return Strings.tr("Localizable", "upgradeAccountPlan.plan.details.transfer", String(describing: p1), fallback: "Transfer: %@") + } + public enum Pricing { + /// On Upgrade Account Plan screen. Displayed conversion for local currency per month on account plan options + public static func localCurrencyPerMonth(_ p1: Any) -> String { + return Strings.tr("Localizable", "upgradeAccountPlan.plan.details.pricing.localCurrencyPerMonth", String(describing: p1), fallback: "%@/month") + } + /// On Upgrade Account Plan screen. Displayed conversion for local currency per year on account plan options + public static func localCurrencyPerYear(_ p1: Any) -> String { + return Strings.tr("Localizable", "upgradeAccountPlan.plan.details.pricing.localCurrencyPerYear", String(describing: p1), fallback: "%@/year") + } + } + } + public enum Tag { + /// On Upgrade Account Plan screen. Displayed tag text for current plan on account plan options + public static let currentPlan = Strings.tr("Localizable", "upgradeAccountPlan.plan.tag.currentPlan", fallback: "Current plan") + /// On Upgrade Account Plan screen. Displayed tag text for recommended plan on account plan options + public static let recommended = Strings.tr("Localizable", "upgradeAccountPlan.plan.tag.recommended", fallback: "Recommended") + } + } + public enum Selection { + public enum Message { + /// On Upgrade Account Plan screen. Snackbar message when selecting current recurring plan on account plan options + public static let alreadyHaveRecurringSubscriptionOfPlan = Strings.tr("Localizable", "upgradeAccountPlan.selection.message.alreadyHaveRecurringSubscriptionOfPlan", fallback: "You already have a recurring subscription for this plan") + } + } + } + public enum VerifyCredentials { + /// Title of the header label in contact verification screen. + public static let headerMessage = Strings.tr("Localizable", "verifyCredentials.headerMessage", fallback: "We protect your data with zero-knowledge encryption so all the information you store, share, and receive on MEGA is secure. To ensure extra security, we ask you to approve the contacts you share information with or receive data from.") + public enum YourCredentials { + /// Title of the label in verification screen. It shows the credentials of the current user so it can be used to be verified by other contacts + public static let title = Strings.tr("Localizable", "verifyCredentials.yourCredentials.title", fallback: "Your credentials") + } + } + public enum Video { + public enum Alert { + public enum ResumeVideo { + /// Alert message shown for video with options to resume playing the video or start from the beginning. %1$s will be replaced by name of the video. %2$s will be replaced by video timestamp + public static func message(_ p1: UnsafePointer, _ p2: UnsafePointer) -> String { + return Strings.tr("Localizable", "video.alert.resumeVideo.message", p1, p2, fallback: "%1$s will resume from %2$s") + } + /// Alert title shown for video with options to resume playing the video or start from the beginning + public static let title = Strings.tr("Localizable", "video.alert.resumeVideo.title", fallback: "Resume video?") + public enum Button { + /// Alert button title that will start playing the video from the beginning + public static let restart = Strings.tr("Localizable", "video.alert.resumeVideo.button.restart", fallback: "Restart") + /// Alert button title that will resume playing the video + public static let resume = Strings.tr("Localizable", "video.alert.resumeVideo.button.resume", fallback: "Resume") + } + } + } + } + } +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +extension Strings { + private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String { + let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table) + return String(format: format, locale: Locale.current, arguments: args) + } +} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type diff --git a/Modules/Localization/MEGAL10n/SwiftGen/swiftgen.yml b/Modules/Localization/MEGAL10n/SwiftGen/swiftgen.yml new file mode 100644 index 0000000000..6649707fb1 --- /dev/null +++ b/Modules/Localization/MEGAL10n/SwiftGen/swiftgen.yml @@ -0,0 +1,14 @@ +input_dir: ../Sources/MEGAL10n/ +output_dir: ../Sources/MEGAL10n + +## Strings +strings: + inputs: + - Resources/Base.lproj + outputs: + - templateName: structured-swift5 + params: + forceFileNameEnum: true + publicAccess: true + enumName: Strings + output: Strings+Generated.swift diff --git a/Modules/Presentation/Features/DeviceCenter/Package.swift b/Modules/Presentation/Features/DeviceCenter/Package.swift index 3d82f1366e..5b05cff5a1 100644 --- a/Modules/Presentation/Features/DeviceCenter/Package.swift +++ b/Modules/Presentation/Features/DeviceCenter/Package.swift @@ -17,7 +17,7 @@ let package = Package( ], dependencies: [ .package(path: "../../../Domain/MEGADomain"), - .package(path: "../MEGAPresentation"), + .package(path: "../../MEGAPresentation"), .package(path: "../../../UI/MEGASwiftUI"), .package(path: "../../../UI/MEGAUIKit"), .package(path: "../../../Infrastracture/MEGATest") diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListView.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListView.swift index 8813cb238f..09335dcfea 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListView.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListView.swift @@ -1,3 +1,4 @@ +import MEGASwiftUI import SwiftUI struct BackupListView: View { @@ -17,13 +18,44 @@ struct BackupListView: View { struct BackupListContentView: View { @ObservedObject var viewModel: BackupListViewModel + @State private var selectedBackupViewModel: DeviceCenterItemViewModel? var body: some View { - List { - ForEach(viewModel.displayedBackups) { backupViewModel in - DeviceCenterItemView(viewModel: backupViewModel) - } - } - .listStyle(.plain) + ListViewContainer( + selectedItem: $selectedBackupViewModel) { + List { + ForEach(viewModel.displayedBackups) { backupViewModel in + DeviceCenterItemView( + viewModel: backupViewModel, + selectedViewModel: $selectedBackupViewModel + ) + } + } + .listStyle(.plain) + .throwingTaskForiOS14 { + try await viewModel.updateDeviceStatusesAndNotify() + } + }.toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Menu { + let menuOptions = viewModel.actionsForDevice() + ForEach(menuOptions.indices, id: \.self) { index in + let contextMenuOption = menuOptions[index] + if contextMenuOption.type == .cameraUploads { + Divider() + } + Button { + contextMenuOption.action() + } label: { + Label(contextMenuOption.title, image: contextMenuOption.icon) + } + } + } label: { + Image("moreList") + .scaledToFit() + .frame(width: 28, height: 28) + } + } + } } } diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListViewModel.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListViewModel.swift index 8df9bbb81f..c8e1352c2d 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListViewModel.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListViewModel.swift @@ -1,19 +1,28 @@ +import Combine import MEGADomain import SwiftUI public final class BackupListViewModel: ObservableObject { - + private let selectedDeviceId: String + private let deviceCenterUseCase: any DeviceCenterUseCaseProtocol private let router: any BackupListRouting - private let backups: [BackupEntity] private let backupListAssets: BackupListAssets private let backupStatuses: [BackupStatus] + private let deviceCenterActions: [DeviceCenterAction] + private let devicesUpdatePublisher: PassthroughSubject<[DeviceEntity], Never> + private let updateInterval: UInt64 + private(set) var backups: [BackupEntity] private var sortedBackupStatuses: [BackupStatusEntity: BackupStatus] { Dictionary(uniqueKeysWithValues: backupStatuses.map { ($0.status, $0) }) } private var sortedBackupTypes: [BackupTypeEntity: BackupType] { Dictionary(uniqueKeysWithValues: backupListAssets.backupTypes.map { ($0.type, $0) }) } - + private var sortedAvailableActions: [DeviceCenterActionType: [DeviceCenterAction]] { + Dictionary(grouping: deviceCenterActions, by: \.type) + } + private var backupsPreloaded: Bool = false + var isFilteredBackupsEmpty: Bool { filteredBackups.isEmpty } @@ -34,23 +43,38 @@ public final class BackupListViewModel: ObservableObject { } init( + selectedDeviceId: String, + devicesUpdatePublisher: PassthroughSubject<[DeviceEntity], Never>, + updateInterval: UInt64, + deviceCenterUseCase: any DeviceCenterUseCaseProtocol, router: any BackupListRouting, backups: [BackupEntity], backupListAssets: BackupListAssets, emptyStateAssets: EmptyStateAssets, searchAssets: SearchAssets, - backupStatuses: [BackupStatus] + backupStatuses: [BackupStatus], + deviceCenterActions: [DeviceCenterAction] ) { + self.selectedDeviceId = selectedDeviceId + self.devicesUpdatePublisher = devicesUpdatePublisher + self.updateInterval = updateInterval + self.deviceCenterUseCase = deviceCenterUseCase self.router = router self.backups = backups self.backupListAssets = backupListAssets self.emptyStateAssets = emptyStateAssets self.searchAssets = searchAssets self.backupStatuses = backupStatuses + self.deviceCenterActions = deviceCenterActions self.isSearchActive = false self.searchText = "" + loadBackupsInitialStatus() + } + + private func loadBackupsInitialStatus() { loadBackupsModels() + backupsPreloaded = true } private func resetFilteredBackups() { @@ -69,14 +93,37 @@ public final class BackupListViewModel: ObservableObject { resetFilteredBackups() } } - + + func updateDeviceStatusesAndNotify() async throws { + while true { + if Task.isCancelled { return } + try await Task.sleep(nanoseconds: updateInterval * 1_000_000_000) + if Task.isCancelled { return } + await syncDevicesAndLoadBackups() + } + } + + func syncDevicesAndLoadBackups() async { + let devices = await deviceCenterUseCase.fetchUserDevices() + await filterAndLoadCurrentDeviceBackups(devices) + devicesUpdatePublisher.send(devices) + } + + @MainActor + func filterAndLoadCurrentDeviceBackups(_ devices: [DeviceEntity]) { + backups = devices.first {$0.id == selectedDeviceId}?.backups ?? [] + loadBackupsModels() + } + func loadBackupsModels() { backupModels = backups .compactMap { backup in - if let assets = loadAssets(for: backup) { + if let assets = loadAssets(for: backup), + let availableActions = actionsForBackup(backup) { return DeviceCenterItemViewModel( itemType: .backup(backup), - assets: assets + assets: assets, + availableActions: availableActions ) } return nil @@ -95,4 +142,38 @@ public final class BackupListViewModel: ObservableObject { status: status ) } + + func actionsForBackup(_ backup: BackupEntity) -> [DeviceCenterAction]? { + var actionTypes = [DeviceCenterActionType]() + + if backup.type == .cameraUpload || backup.type == .mediaUpload { + actionTypes.append(.showInCD) + } else { + actionTypes.append(.showInBackups) + } + + actionTypes.append(.info) + + return actionTypes.compactMap { type in + sortedAvailableActions[type]?.first + } + } + + func actionsForDevice() -> [DeviceCenterAction] { + let currentDeviceUUID = UIDevice.current.identifierForVendor?.uuidString ?? "" + let currentDeviceId = deviceCenterUseCase.loadCurrentDeviceId() + let isMobileDevice = backups.contains { + $0.type == .cameraUpload || $0.type == .mediaUpload + } + + var actionTypes: [DeviceCenterActionType] = [.rename, .info] + + if selectedDeviceId == currentDeviceUUID || (selectedDeviceId == currentDeviceId && isMobileDevice) { + actionTypes.append(.cameraUploads) + } + + return actionTypes.compactMap { type in + sortedAvailableActions[type]?.first + } + } } diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListViewRouter.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListViewRouter.swift index 2e88651c23..b3297b777a 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListViewRouter.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/BackupList/BackupListViewRouter.swift @@ -1,3 +1,4 @@ +import Combine import MEGADomain import MEGAPresentation import SwiftUI @@ -8,44 +9,64 @@ public protocol BackupListRouting: Routing { public final class BackupListViewRouter: NSObject, BackupListRouting { private weak var baseViewController: UIViewController? private weak var navigationController: UINavigationController? - private let deviceName: String + private let selectedDeviceId: String + private let selectedDeviceName: String + private let devicesUpdatePublisher: PassthroughSubject<[DeviceEntity], Never> + private let updateInterval: UInt64 private let backups: [BackupEntity] + private let deviceCenterUseCase: any DeviceCenterUseCaseProtocol private let backupListAssets: BackupListAssets private let emptyStateAssets: EmptyStateAssets private let searchAssets: SearchAssets private let backupStatuses: [BackupStatus] + private let deviceCenterActions: [DeviceCenterAction] public init( - deviceName: String, + selectedDeviceId: String, + selectedDeviceName: String, + devicesUpdatePublisher: PassthroughSubject<[DeviceEntity], Never>, + updateInterval: UInt64, backups: [BackupEntity], + deviceCenterUseCase: any DeviceCenterUseCaseProtocol, navigationController: UINavigationController?, backupListAssets: BackupListAssets, emptyStateAssets: EmptyStateAssets, searchAssets: SearchAssets, - backupStatuses: [BackupStatus] + backupStatuses: [BackupStatus], + deviceCenterActions: [DeviceCenterAction] ) { - self.deviceName = deviceName + self.selectedDeviceId = selectedDeviceId + self.selectedDeviceName = selectedDeviceName + self.devicesUpdatePublisher = devicesUpdatePublisher + self.updateInterval = updateInterval self.backups = backups + self.deviceCenterUseCase = deviceCenterUseCase self.navigationController = navigationController self.backupListAssets = backupListAssets self.emptyStateAssets = emptyStateAssets self.searchAssets = searchAssets self.backupStatuses = backupStatuses + self.deviceCenterActions = deviceCenterActions } public func build() -> UIViewController { let backupListViewModel = BackupListViewModel( + selectedDeviceId: selectedDeviceId, + devicesUpdatePublisher: devicesUpdatePublisher, + updateInterval: updateInterval, + deviceCenterUseCase: deviceCenterUseCase, router: self, backups: backups, backupListAssets: backupListAssets, emptyStateAssets: emptyStateAssets, searchAssets: searchAssets, - backupStatuses: backupStatuses + backupStatuses: backupStatuses, + deviceCenterActions: deviceCenterActions ) let backupListView = BackupListView(viewModel: backupListViewModel) let hostingController = UIHostingController(rootView: backupListView) baseViewController = hostingController - baseViewController?.title = deviceName + baseViewController?.title = selectedDeviceName return hostingController } diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterItem/DeviceCenterItemView.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterItem/DeviceCenterItemView.swift index a31cfacc1d..58ca188b3a 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterItem/DeviceCenterItemView.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterItem/DeviceCenterItemView.swift @@ -3,6 +3,7 @@ import SwiftUI struct DeviceCenterItemView: View { @Environment(\.colorScheme) private var colorScheme @ObservedObject var viewModel: DeviceCenterItemViewModel + @Binding var selectedViewModel: DeviceCenterItemViewModel? var body: some View { HStack { @@ -43,12 +44,14 @@ struct DeviceCenterItemView: View { } Spacer() Button { - // Add button action here + selectedViewModel = viewModel } label: { Image("moreList") .scaledToFit() .frame(width: 28, height: 28) - }.buttonStyle(.borderless) + } + .buttonStyle(.borderless) + .frame(width: 60, height: 60) } .contentShape(Rectangle()) .onTapGesture { diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterItem/DeviceCenterItemViewModel.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterItem/DeviceCenterItemViewModel.swift index 89213e7633..7bce5249d2 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterItem/DeviceCenterItemViewModel.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterItem/DeviceCenterItemViewModel.swift @@ -1,15 +1,11 @@ import MEGADomain import SwiftUI -public class DeviceCenterItemViewModel: ObservableObject, Identifiable { - enum ItemType { - case backup(BackupEntity) - case device(DeviceEntity) - } - +public class DeviceCenterItemViewModel: ObservableObject, Identifiable { private let router: (any DeviceListRouting)? - private var itemType: ItemType + private var itemType: DeviceCenterItemType var assets: ItemAssets + var availableActions: [DeviceCenterAction] @Published var name: String = "" @Published var iconName: String? @@ -20,11 +16,13 @@ public class DeviceCenterItemViewModel: ObservableObject, Identifiable { @Published var backupPercentage: String = "" init(router: (any DeviceListRouting)? = nil, - itemType: ItemType, - assets: ItemAssets) { + itemType: DeviceCenterItemType, + assets: ItemAssets, + availableActions: [DeviceCenterAction]) { self.router = router self.itemType = itemType self.assets = assets + self.availableActions = availableActions self.configure() } diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterListContainer/DeviceCenterListContainerView.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterListContainer/DeviceCenterListContainerView.swift new file mode 100644 index 0000000000..b1e78d53a5 --- /dev/null +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceCenterListContainer/DeviceCenterListContainerView.swift @@ -0,0 +1,53 @@ +import MEGASwiftUI +import SwiftUI + +struct ListViewContainer: View where Content: View { + @Binding var selectedItem: DeviceCenterItemViewModel? + let content: () -> Content + let sheetHeaderHeight: CGFloat = 75 + let sheetButtonsHeight: CGFloat = 60 + let sheetBottomPadding: CGFloat = 30 + + var body: some View { + content() + .sheet(item: $selectedItem) { selectedItem in + if #available(iOS 16, *) { + sheetContent( + selectedItem: selectedItem + ).presentationDetents([ + .height((CGFloat(selectedItem.availableActions.count) * sheetButtonsHeight) + sheetHeaderHeight + sheetBottomPadding) + ]) + } else { + sheetContent( + selectedItem: selectedItem + ) + } + } + } + + @ViewBuilder + private func sheetContent( + selectedItem: DeviceCenterItemViewModel + ) -> some View { + ActionSheetContentView( + headerView: + ActionSheetHeaderView( + iconName: selectedItem.iconName ?? "", + title: selectedItem.name, + detailImageName: selectedItem.statusIconName ?? "", + subtitle: selectedItem.statusTitle, + subtitleColorName: selectedItem.statusColorName + ), + actionButtons: { + selectedItem.availableActions.compactMap { + ActionSheetButton( + icon: $0.icon, + title: $0.title, + subtitle: $0.subtitle, + action: $0.action + ) + } + }() + ) + } +} diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListView.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListView.swift index fb5aed914a..2d43533055 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListView.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListView.swift @@ -1,3 +1,4 @@ +import MEGASwiftUI import SwiftUI struct DeviceListView: View { @@ -17,29 +18,45 @@ struct DeviceListView: View { struct DeviceListContentView: View { @ObservedObject var viewModel: DeviceListViewModel + @State private var selectedViewModel: DeviceCenterItemViewModel? var body: some View { - List { - if viewModel.isFiltered { - ForEach(viewModel.filteredDevices) { deviceViewModel in - DeviceCenterItemView(viewModel: deviceViewModel) - } - } else { - Section(header: Text(viewModel.deviceListAssets.currentDeviceTitle)) { - if let currentDeviceVM = viewModel.currentDevice { - DeviceCenterItemView(viewModel: currentDeviceVM) - } - } - - if viewModel.otherDevices.isNotEmpty { - Section(header: Text(viewModel.deviceListAssets.otherDevicesTitle)) { - ForEach(viewModel.otherDevices) { deviceViewModel in - DeviceCenterItemView(viewModel: deviceViewModel) + ListViewContainer( + selectedItem: $selectedViewModel) { + List { + if viewModel.isFiltered { + ForEach(viewModel.filteredDevices) { deviceViewModel in + DeviceCenterItemView( + viewModel: deviceViewModel, + selectedViewModel: $selectedViewModel + ) + } + } else { + Section(header: Text(viewModel.deviceListAssets.currentDeviceTitle)) { + if let currentDeviceVM = viewModel.currentDevice { + DeviceCenterItemView( + viewModel: currentDeviceVM, + selectedViewModel: $selectedViewModel + ) + } + } + + if viewModel.otherDevices.isNotEmpty { + Section(header: Text(viewModel.deviceListAssets.otherDevicesTitle)) { + ForEach(viewModel.otherDevices) { deviceViewModel in + DeviceCenterItemView( + viewModel: deviceViewModel, + selectedViewModel: $selectedViewModel + ) + } + } } } } + .listStyle(.plain) + .throwingTaskForiOS14 { + try await viewModel.startAutoRefreshUserDevices() + } } - } - .listStyle(.plain) } } diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListViewModel.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListViewModel.swift index 50447aa959..b47749f84a 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListViewModel.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListViewModel.swift @@ -1,3 +1,4 @@ +import Combine import MEGADomain import SwiftUI @@ -5,9 +6,18 @@ public final class DeviceListViewModel: ObservableObject { private let router: any DeviceListRouting private let deviceCenterUseCase: any DeviceCenterUseCaseProtocol private let backupStatuses: [BackupStatus] + private let deviceCenterActions: [DeviceCenterAction] private var sortedBackupStatuses: [BackupStatusEntity: BackupStatus] { Dictionary(uniqueKeysWithValues: backupStatuses.map { ($0.status, $0) }) } + private var sortedAvailableActions: [DeviceCenterActionType: [DeviceCenterAction]] { + Dictionary(grouping: deviceCenterActions, by: \.type) + } + private var cancellable: Set = [] + private let devicesUpdatePublisher: PassthroughSubject<[DeviceEntity], Never> + private let updateInterval: UInt64 + private var currentDeviceId: String? + private let currentDeviceUUID: String var isFilteredDevicesEmpty: Bool { filteredDevices.isEmpty @@ -31,47 +41,33 @@ public final class DeviceListViewModel: ObservableObject { } init( + devicesUpdatePublisher: PassthroughSubject<[DeviceEntity], Never>, + updateInterval: UInt64, router: any DeviceListRouting, deviceCenterUseCase: any DeviceCenterUseCaseProtocol, deviceListAssets: DeviceListAssets, emptyStateAssets: EmptyStateAssets, searchAssets: SearchAssets, - backupStatuses: [BackupStatus] + backupStatuses: [BackupStatus], + deviceCenterActions: [DeviceCenterAction] ) { + self.devicesUpdatePublisher = devicesUpdatePublisher + self.updateInterval = updateInterval self.router = router self.deviceCenterUseCase = deviceCenterUseCase self.deviceListAssets = deviceListAssets self.emptyStateAssets = emptyStateAssets self.searchAssets = searchAssets self.backupStatuses = backupStatuses + self.deviceCenterActions = deviceCenterActions self.isSearchActive = false self.searchText = "" + self.currentDeviceUUID = UIDevice.current.identifierForVendor?.uuidString ?? "" + setupDevicesUpdateSubscription() loadUserDevices() } - func fetchUserDevices() async -> [DeviceEntity] { - await deviceCenterUseCase.fetchUserDevices() - } - - @MainActor - func arrangeDevices(_ devices: [DeviceEntity]) { - let currentDeviceId = deviceCenterUseCase.loadCurrentDeviceId() - - if let device = devices.first(where: { $0.id == currentDeviceId }), - let currentDeviceVM = loadDeviceViewModel(device) { - currentDevice = currentDeviceVM - } else { - loadDefaultDevice() - } - - otherDevices = devices - .filter { $0.id != currentDeviceId } - .compactMap(loadDeviceViewModel) - - resetFilteredDevices() - } - private func loadUserDevices() { Task { let userDevices = await fetchUserDevices() @@ -99,20 +95,22 @@ public final class DeviceListViewModel: ObservableObject { } private func loadDeviceViewModel(_ device: DeviceEntity) -> DeviceCenterItemViewModel? { - guard let deviceAssets = loadAssets(for: device) else { + guard let deviceAssets = loadAssets(for: device), + let availableActions = actionsForDevice(device) else { return nil } return DeviceCenterItemViewModel( router: router, itemType: .device(device), - assets: deviceAssets + assets: deviceAssets, + availableActions: availableActions ) } private func loadDefaultDevice() { let device = DeviceEntity( - id: UIDevice.current.identifierForVendor?.uuidString ?? "", + id: currentDeviceUUID, name: UIDevice.current.name, status: .noCameraUploads ) @@ -132,4 +130,61 @@ public final class DeviceListViewModel: ObservableObject { defaultName: deviceListAssets.deviceDefaultName ) } + + private func setupDevicesUpdateSubscription() { + devicesUpdatePublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] devices in + guard let self = self else { return } + Task { + await self.arrangeDevices(devices) + } + } + .store(in: &cancellable) + } + + func startAutoRefreshUserDevices() async throws { + while true { + if Task.isCancelled { return } + try await Task.sleep(nanoseconds: updateInterval * 1_000_000_000) + if Task.isCancelled { return } + loadUserDevices() + } + } + + func fetchUserDevices() async -> [DeviceEntity] { + await deviceCenterUseCase.fetchUserDevices() + } + + @MainActor + func arrangeDevices(_ devices: [DeviceEntity]) { + currentDeviceId = deviceCenterUseCase.loadCurrentDeviceId() + + if let device = devices.first(where: { $0.id == currentDeviceId }), + let currentDeviceVM = loadDeviceViewModel(device) { + currentDevice = currentDeviceVM + } else { + loadDefaultDevice() + } + + otherDevices = devices + .filter { $0.id != currentDeviceId } + .compactMap(loadDeviceViewModel) + + resetFilteredDevices() + } + + func actionsForDevice(_ device: DeviceEntity) -> [DeviceCenterAction]? { + var actionTypes = [DeviceCenterActionType]() + + if device.id == currentDeviceUUID || (device.id == currentDeviceId && device.isMobileDevice()) { + actionTypes.append(.cameraUploads) + } + + actionTypes.append(contentsOf: [.rename, .info]) + + return actionTypes.compactMap { type in + sortedAvailableActions[type]?.first + } + } } diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListViewRouter.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListViewRouter.swift index c9c05e6748..1a9dc34f4b 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListViewRouter.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/DeviceList/DeviceListViewRouter.swift @@ -1,3 +1,4 @@ +import Combine import MEGADomain import MEGAPresentation import MEGAUIKit @@ -10,6 +11,8 @@ public protocol DeviceListRouting: Routing { public final class DeviceListViewRouter: NSObject, DeviceListRouting { private weak var baseViewController: UIViewController? private weak var navigationController: UINavigationController? + private let devicesUpdatePublisher: PassthroughSubject<[DeviceEntity], Never> + private let updateInterval: UInt64 private let deviceCenterAssets: DeviceCenterAssets private let deviceCenterUseCase: any DeviceCenterUseCaseProtocol @@ -22,17 +25,23 @@ public final class DeviceListViewRouter: NSObject, DeviceListRouting { self.deviceCenterAssets = deviceCenterAssets self.deviceCenterUseCase = deviceCenterUseCase + devicesUpdatePublisher = PassthroughSubject<[DeviceEntity], Never>() + updateInterval = 30 + super.init() } public func build() -> UIViewController { let deviceListViewModel = DeviceListViewModel( + devicesUpdatePublisher: devicesUpdatePublisher, + updateInterval: updateInterval, router: self, deviceCenterUseCase: deviceCenterUseCase, deviceListAssets: deviceCenterAssets.deviceListAssets, emptyStateAssets: deviceCenterAssets.emptyStateAssets, searchAssets: deviceCenterAssets.searchAssets, - backupStatuses: deviceCenterAssets.backupStatuses + backupStatuses: deviceCenterAssets.backupStatuses, + deviceCenterActions: deviceCenterAssets.deviceCenterActions ) let deviceListView = DeviceListView(viewModel: deviceListViewModel) let hostingController = UIHostingController(rootView: deviceListView) @@ -51,13 +60,18 @@ public final class DeviceListViewRouter: NSObject, DeviceListRouting { guard let backups = device.backups else { return } BackupListViewRouter( - deviceName: device.name.isEmpty ? deviceCenterAssets.deviceListAssets.deviceDefaultName : device.name, + selectedDeviceId: device.id, + selectedDeviceName: device.name.isEmpty ? deviceCenterAssets.deviceListAssets.deviceDefaultName : device.name, + devicesUpdatePublisher: devicesUpdatePublisher, + updateInterval: updateInterval, backups: backups, + deviceCenterUseCase: deviceCenterUseCase, navigationController: navigationController, backupListAssets: deviceCenterAssets.backupListAssets, emptyStateAssets: deviceCenterAssets.emptyStateAssets, searchAssets: deviceCenterAssets.searchAssets, - backupStatuses: deviceCenterAssets.backupStatuses + backupStatuses: deviceCenterAssets.backupStatuses, + deviceCenterActions: deviceCenterAssets.deviceCenterActions ).start() } } diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/BackupListAssets.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/BackupListAssets.swift index f2c8573543..5b0edac28c 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/BackupListAssets.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/BackupListAssets.swift @@ -1,4 +1,3 @@ - public struct BackupListAssets { public let backupTypes: [BackupType] diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterAction.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterAction.swift new file mode 100644 index 0000000000..9814a7f78a --- /dev/null +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterAction.swift @@ -0,0 +1,23 @@ +public enum DeviceCenterActionType { + case cameraUploads + case info + case rename + case showInCD + case showInBackups +} + +public struct DeviceCenterAction { + let type: DeviceCenterActionType + let title: String + let subtitle: String? + let icon: String + let action: () -> Void + + public init(type: DeviceCenterActionType, title: String, subtitle: String? = nil, icon: String, action: @escaping () -> Void) { + self.type = type + self.title = title + self.subtitle = subtitle + self.icon = icon + self.action = action + } +} diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterAssets.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterAssets.swift index 3fbbf07f00..5a00e36a0b 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterAssets.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterAssets.swift @@ -6,18 +6,21 @@ public struct DeviceCenterAssets { public let emptyStateAssets: EmptyStateAssets public let searchAssets: SearchAssets public let backupStatuses: [BackupStatus] + public let deviceCenterActions: [DeviceCenterAction] public init( deviceListAssets: DeviceListAssets, backupListAssets: BackupListAssets, emptyStateAssets: EmptyStateAssets, searchAssets: SearchAssets, - backupStatuses: [BackupStatus] + backupStatuses: [BackupStatus], + deviceCenterActions: [DeviceCenterAction] ) { self.deviceListAssets = deviceListAssets self.backupListAssets = backupListAssets self.emptyStateAssets = emptyStateAssets self.searchAssets = searchAssets self.backupStatuses = backupStatuses + self.deviceCenterActions = deviceCenterActions } } diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterItemType.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterItemType.swift new file mode 100644 index 0000000000..0701902379 --- /dev/null +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceCenterItemType.swift @@ -0,0 +1,6 @@ +import MEGADomain + +public enum DeviceCenterItemType { + case backup(BackupEntity) + case device(DeviceEntity) +} diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceListAssets.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceListAssets.swift index d6f957024e..fe9d73952e 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceListAssets.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/DeviceListAssets.swift @@ -1,4 +1,3 @@ - public struct DeviceListAssets { public let title: String public let currentDeviceTitle: String diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/EmptyStateAssets.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/EmptyStateAssets.swift index dbdf6c68dc..644eb73356 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/EmptyStateAssets.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/EmptyStateAssets.swift @@ -1,4 +1,3 @@ - public struct EmptyStateAssets { public let image: String public let title: String diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/ItemAssets.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/ItemAssets.swift index d7dc8b3677..cf40bdd109 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/ItemAssets.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/ItemAssets.swift @@ -1,4 +1,3 @@ - public struct ItemAssets { public let iconName: String public let backupStatus: BackupStatus diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/SearchAssets.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/SearchAssets.swift index 601d2f213a..a515b7ff1b 100644 --- a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/SearchAssets.swift +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenter/Model/Assets/SearchAssets.swift @@ -1,4 +1,3 @@ - public struct SearchAssets { public let placeHolder: String public let cancelTitle: String diff --git a/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenterMocks/Models/DeviceCenterAction+Test.swift b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenterMocks/Models/DeviceCenterAction+Test.swift new file mode 100644 index 0000000000..a6ed55a4ad --- /dev/null +++ b/Modules/Presentation/Features/DeviceCenter/Sources/DeviceCenterMocks/Models/DeviceCenterAction+Test.swift @@ -0,0 +1,10 @@ +import DeviceCenter + +public extension DeviceCenterAction { + init( + type: DeviceCenterActionType, + isTesting: Bool = true + ) { + self.init(type: type, title: "", icon: "", action: {}) + } +} diff --git a/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/Routers/BackupListViewRouterTests.swift b/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/Routers/BackupListViewRouterTests.swift index 9d19798ec9..b193f72b75 100644 --- a/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/Routers/BackupListViewRouterTests.swift +++ b/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/Routers/BackupListViewRouterTests.swift @@ -1,4 +1,7 @@ +import Combine @testable import DeviceCenter +import MEGADomain +import MEGADomainMock import MEGATest import SwiftUI import XCTest @@ -33,8 +36,12 @@ final class BackupListsViewRouterTests: XCTestCase { let mockPresenter = UINavigationController() let sut = BackupListViewRouter( - deviceName: "Device 1", + selectedDeviceId: "1", + selectedDeviceName: "Device 1", + devicesUpdatePublisher: PassthroughSubject<[DeviceEntity], Never>(), + updateInterval: 1, backups: [], + deviceCenterUseCase: MockDeviceCenterUseCase(), navigationController: mockPresenter, backupListAssets: BackupListAssets( @@ -53,7 +60,8 @@ final class BackupListsViewRouterTests: XCTestCase { ), backupStatuses: [ BackupStatus(status: .upToDate, title: "", colorName: "blue", iconName: "circle.fill") - ] + ], + deviceCenterActions: [] ) trackForMemoryLeaks(on: sut, file: file, line: line) diff --git a/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/Routers/DeviceListViewRouterTests.swift b/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/Routers/DeviceListViewRouterTests.swift index 86d3d93a62..3989b7e2c3 100644 --- a/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/Routers/DeviceListViewRouterTests.swift +++ b/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/Routers/DeviceListViewRouterTests.swift @@ -37,7 +37,8 @@ final class DeviceListViewRouterTests: XCTestCase { title: "Device List", currentDeviceTitle: "This device", otherDevicesTitle: "Other devices", - deviceDefaultName: "Unknown Device"), + deviceDefaultName: "Unknown Device" + ), backupListAssets: BackupListAssets( backupTypes: [ @@ -55,7 +56,8 @@ final class DeviceListViewRouterTests: XCTestCase { ), backupStatuses: [ BackupStatus(status: .upToDate, title: "", colorName: "blue", iconName: "circle.fill") - ] + ], + deviceCenterActions: [] ) let sut = DeviceListViewRouter( diff --git a/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/ViewModels/BackupListViewModelTests.swift b/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/ViewModels/BackupListViewModelTests.swift index 4c1af87904..a33fa6e9ae 100644 --- a/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/ViewModels/BackupListViewModelTests.swift +++ b/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/ViewModels/BackupListViewModelTests.swift @@ -1,3 +1,4 @@ +import Combine @testable import DeviceCenter import DeviceCenterMocks import MEGADomain @@ -5,10 +6,20 @@ import MEGADomainMock import XCTest final class BackupListViewModelTests: XCTestCase { + let mockCurrentDeviceId = "1" + let mockAuxDeviceId = "2" + let devicesUpdatePublisher = PassthroughSubject<[DeviceEntity], Never>() func test_loadAssets_matchesBackupStatuses() { - var backup = BackupEntity(id: 1, name: "backup1") - let viewModel = makeSUT(backups: [backup]) + var backup = BackupEntity( + id: 1, + name: "backup1" + ) + + let viewModel = makeSUT( + currentDeviceId: mockCurrentDeviceId, + backups: [backup] + ) for status in backupStatusEntities() { backup.backupStatus = status @@ -17,24 +28,26 @@ final class BackupListViewModelTests: XCTestCase { } func testLoadBackupsModels_backupsWithDifferentStatus_loadsBackupModels() { - var backup1 = BackupEntity(id: 1, name: "backup1") - backup1.backupStatus = .updating - var backup2 = BackupEntity(id: 2, name: "backup2") - backup2.backupStatus = .upToDate + let backups = backups() + let viewModel = makeSUT( + currentDeviceId: mockCurrentDeviceId, + backups: backups + ) - let viewModel = makeSUT(backups: [backup1, backup2]) viewModel.loadBackupsModels() - XCTAssertEqual(viewModel.backupModels.count, 2) + XCTAssertEqual(viewModel.backupModels.count, 3) + + let expectedBackupNames = backups.map(\.name) + let foundBackupNames = viewModel.backupModels.map(\.name) + XCTAssertEqual(foundBackupNames, expectedBackupNames) } func testFilterBackups_withSearchText_matchingBackupName() { - var backup1 = BackupEntity(id: 1, name: "backup1") - backup1.backupStatus = .updating - var backup2 = BackupEntity(id: 2, name: "backup2") - backup2.backupStatus = .upToDate - - let viewModel = makeSUT(backups: [backup1, backup2]) + let viewModel = makeSUT( + currentDeviceId: mockCurrentDeviceId, + backups: backups() + ) viewModel.searchText = "1" @@ -53,17 +66,234 @@ final class BackupListViewModelTests: XCTestCase { } func testFilterBackups_withEmptySearchText_shouldReturnTheSameBackups() { - var backup1 = BackupEntity(id: 1, name: "backup1") + let backups = backups() + let viewModel = makeSUT( + currentDeviceId: mockCurrentDeviceId, + backups: backups + ) + + viewModel.searchText = "" + + let expectedBackupNames = backups.map(\.name) + let foundBackupNames = viewModel.backupModels.map(\.name) + XCTAssertEqual(foundBackupNames, expectedBackupNames) + } + + func testFilterAndLoadCurrentDeviceBackups_loadsBackupsForCurrentDevice() async { + let backups = backups() + let userGroupedBackups = Dictionary(grouping: backups, by: \.deviceId) + + let viewModel = makeSUT( + currentDeviceId: mockCurrentDeviceId, + backups: userGroupedBackups[mockCurrentDeviceId] ?? [] + ) + + let expectedBackupNames = userGroupedBackups[mockCurrentDeviceId]?.map(\.name) + let foundBackupNames = viewModel.backups.map(\.name) + + await viewModel.filterAndLoadCurrentDeviceBackups(devices()) + XCTAssertEqual(foundBackupNames, expectedBackupNames) + + let viewModel2 = makeSUT( + currentDeviceId: mockAuxDeviceId, + backups: userGroupedBackups[mockAuxDeviceId] ?? [] + ) + + await viewModel2.filterAndLoadCurrentDeviceBackups(devices()) + + let expectedBackupNames2 = userGroupedBackups[mockAuxDeviceId]?.map(\.name) + let foundBackupNames2 = viewModel2.backups.map(\.name) + + XCTAssertEqual(foundBackupNames2, expectedBackupNames2) + } + + func testUpdateDeviceStatusesAndNotify_fetchesAndUpdateDevices() async { + let backups = backups() + let userGroupedBackups = Dictionary(grouping: backups, by: \.deviceId) + let mockUseCase = MockDeviceCenterUseCase(devices: devices(), currentDeviceId: mockCurrentDeviceId) + + let viewModel = makeSUT( + currentDeviceId: mockCurrentDeviceId, + backups: userGroupedBackups[mockCurrentDeviceId] ?? [], + deviceCenterUseCase: mockUseCase + ) + + await viewModel.syncDevicesAndLoadBackups() + let expectedBackupNames = userGroupedBackups[mockCurrentDeviceId]?.map(\.name) + let foundBackupNames = viewModel.backups.map(\.name) + + XCTAssertEqual(foundBackupNames, expectedBackupNames) + } + + func testUpdateDeviceStatusesAndNotify_cancellation() async throws { + let backups = backups() + let userGroupedBackups = Dictionary(grouping: backups, by: \.deviceId) + let mockUseCase = MockDeviceCenterUseCase(devices: devices(), currentDeviceId: mockCurrentDeviceId) + + let viewModel = makeSUT( + currentDeviceId: mockCurrentDeviceId, + backups: userGroupedBackups[mockCurrentDeviceId] ?? [], + deviceCenterUseCase: mockUseCase, + updateInterval: 5 + ) + + let task = Task { + try await viewModel.updateDeviceStatusesAndNotify() + } + + task.cancel() + + XCTAssertTrue(task.isCancelled, "Task should be cancelled") + } + + func testActionsForBackup_cameraUploadBackupType_returnsCorrectActions() { + let backup = BackupEntity( + id: 1, + name: "backup1", + deviceId: mockCurrentDeviceId, + type: .cameraUpload + ) + + let backups = [backup] + let mockUseCase = MockDeviceCenterUseCase(devices: devices(), currentDeviceId: mockCurrentDeviceId) + let userGroupedBackups = Dictionary(grouping: backups, by: \.deviceId) + + let viewModel = makeSUT( + currentDeviceId: mockCurrentDeviceId, + backups: userGroupedBackups[mockCurrentDeviceId] ?? [], + deviceCenterUseCase: mockUseCase + ) + + let actions = viewModel.actionsForBackup(backup) + let actionsType = actions?.compactMap {$0.type} + let expectedActions: [DeviceCenterActionType] = [.showInCD, .info] + + XCTAssertEqual(actionsType, expectedActions, "Actions for camera upload backup are incorrect") + } + + func testActionsForBackup_otherBackupType_returnsCorrectActions() { + let backup = BackupEntity( + id: 1, + name: "backup1", + deviceId: mockCurrentDeviceId, + type: .downSync + ) + + let backups = [backup] + let mockUseCase = MockDeviceCenterUseCase(devices: devices(), currentDeviceId: mockCurrentDeviceId) + let userGroupedBackups = Dictionary(grouping: backups, by: \.deviceId) + + let viewModel = makeSUT( + currentDeviceId: mockCurrentDeviceId, + backups: userGroupedBackups[mockCurrentDeviceId] ?? [], + deviceCenterUseCase: mockUseCase + ) + + let actions = viewModel.actionsForBackup(backup) + let actionsType = actions?.compactMap {$0.type} + let expectedActions: [DeviceCenterActionType] = [.showInBackups, .info] + + XCTAssertEqual(actionsType, expectedActions, "Actions for backup are incorrect") + } + + func testActionsForDevice_currentDeviceWithCameraUpload_returnsCorrectActions() { + let backup = BackupEntity( + id: 1, + name: "backup1", + deviceId: mockCurrentDeviceId, + type: .cameraUpload + ) + + let backups = [backup] + + let userGroupedBackups = Dictionary(grouping: backups, by: \.deviceId) + let mockUseCase = MockDeviceCenterUseCase(devices: devices(), currentDeviceId: mockCurrentDeviceId) + + let viewModel = makeSUT( + currentDeviceId: mockCurrentDeviceId, + backups: userGroupedBackups[mockCurrentDeviceId] ?? [], + deviceCenterUseCase: mockUseCase + ) + + let actions = viewModel.actionsForDevice() + + let expectedActions: [DeviceCenterActionType] = [.rename, .info, .cameraUploads] + let actionsType = actions.compactMap {$0.type} + XCTAssertEqual(actionsType, expectedActions, "Actions for the current device are incorrect") + } + + func testActionsForDevice_currentDeviceWithoutCameraUpload_returnsCorrectActions() { + let backup = BackupEntity( + id: 1, + name: "backup1", + deviceId: mockCurrentDeviceId, + type: .upSync + ) + + let backups = [backup] + + let userGroupedBackups = Dictionary(grouping: backups, by: \.deviceId) + let mockUseCase = MockDeviceCenterUseCase(devices: devices(), currentDeviceId: mockCurrentDeviceId) + + let viewModel = makeSUT( + currentDeviceId: mockCurrentDeviceId, + backups: userGroupedBackups[mockCurrentDeviceId] ?? [], + deviceCenterUseCase: mockUseCase + ) + + let actions = viewModel.actionsForDevice() + + let expectedActions: [DeviceCenterActionType] = [.rename, .info] + let actionsType = actions.compactMap {$0.type} + XCTAssertEqual(actionsType, expectedActions, "Actions for the current device are incorrect") + } + + private func backups() -> [BackupEntity] { + var backup1 = BackupEntity( + id: 1, + name: "backup1", + deviceId: mockCurrentDeviceId + ) + backup1.backupStatus = .updating - var backup2 = BackupEntity(id: 2, name: "backup2") + + var backup2 = BackupEntity( + id: 2, + name: "backup2", + deviceId: mockCurrentDeviceId + ) + backup2.backupStatus = .upToDate - let backups = [backup1, backup2] - let viewModel = makeSUT(backups: backups) + var backup3 = BackupEntity( + id: 3, + name: "backup3", + deviceId: mockAuxDeviceId + ) - viewModel.searchText = "" + backup3.backupStatus = .offline - XCTAssertEqual(viewModel.filteredBackups.map(\.name), backups.map(\.name)) + return [backup1, backup2, backup3] + } + + private func devices() -> [DeviceEntity] { + let userGroupedBackups = Dictionary(grouping: backups(), by: \.deviceId) + + let device1 = DeviceEntity( + id: mockCurrentDeviceId, + name: "device1", + backups: userGroupedBackups[mockCurrentDeviceId], + status: .upToDate + ) + + let device2 = DeviceEntity( + id: "2", + name: "device2", + backups: userGroupedBackups[mockAuxDeviceId], + status: .upToDate + ) + + return [device1, device2] } private func backupStatusEntities() -> [BackupStatusEntity] { @@ -80,10 +310,22 @@ final class BackupListViewModelTests: XCTestCase { XCTAssertEqual(assets?.backupStatus.status, backupEntity.backupStatus) } - private func makeSUT(backups: [BackupEntity], file: StaticString = #file, line: UInt = #line) -> BackupListViewModel { + private func makeSUT( + currentDeviceId: String, + backups: [BackupEntity], + deviceCenterUseCase: MockDeviceCenterUseCase = MockDeviceCenterUseCase(), + updateInterval: UInt64 = 1, + file: StaticString = #file, + line: UInt = #line + ) -> BackupListViewModel { + let backupStatusEntities = backupStatusEntities() let backupTypeEntities = backupTypeEntities() let sut = BackupListViewModel( + selectedDeviceId: currentDeviceId, + devicesUpdatePublisher: devicesUpdatePublisher, + updateInterval: updateInterval, + deviceCenterUseCase: deviceCenterUseCase, router: MockBackupListViewRouter(), backups: backups, backupListAssets: @@ -99,7 +341,24 @@ final class BackupListViewModelTests: XCTestCase { placeHolder: "", cancelTitle: "" ), - backupStatuses: backupStatusEntities.compactMap { BackupStatus(status: $0) } + backupStatuses: backupStatusEntities.compactMap { BackupStatus(status: $0) }, + deviceCenterActions: [ + DeviceCenterAction( + type: .cameraUploads + ), + DeviceCenterAction( + type: .info + ), + DeviceCenterAction( + type: .rename + ), + DeviceCenterAction( + type: .showInBackups + ), + DeviceCenterAction( + type: .showInCD + ) + ] ) trackForMemoryLeaks(on: sut, file: file, line: line) diff --git a/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/ViewModels/DeviceListViewModelTests.swift b/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/ViewModels/DeviceListViewModelTests.swift index fee48a29ca..0c17947eb7 100644 --- a/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/ViewModels/DeviceListViewModelTests.swift +++ b/Modules/Presentation/Features/DeviceCenter/Tests/DeviceCenterTests/ViewModels/DeviceListViewModelTests.swift @@ -1,3 +1,4 @@ +import Combine @testable import DeviceCenter import DeviceCenterMocks import MEGADomain @@ -7,9 +8,39 @@ import SwiftUI import XCTest final class DeviceListViewModelTests: XCTestCase { - let mockCurrentDeviceId = "1" + let mockAuxDeviceId = "2" + + func testLoadUserDevices_returnsUserDevices() async { + let devices = devices() + let viewModel = makeSUT( + devices: devices, + currentDeviceId: mockCurrentDeviceId + ) + + let fetchedDevices = await viewModel.fetchUserDevices() + await viewModel.arrangeDevices(fetchedDevices) + + XCTAssertEqual(fetchedDevices.map(\.name), devices.map(\.name)) + } + func testArrangeDevices_withCurrentDeviceId_loadsCurrentDevice() async throws { + let devices = devices() + let currentDevice = devices.first {$0.id == mockCurrentDeviceId} + let viewModel = makeSUT( + devices: devices, + currentDeviceId: mockCurrentDeviceId + ) + + let userDevices = await viewModel.fetchUserDevices() + await viewModel.arrangeDevices(userDevices) + + let currentDeviceName = try XCTUnwrap(currentDevice?.name) + XCTAssertEqual(viewModel.currentDevice?.name, currentDeviceName) + XCTAssertTrue(viewModel.otherDevices.isNotEmpty) + XCTAssertTrue(viewModel.otherDevices.count == 1) + } + func testFilterDevices_withSearchText_matchingDeviceName() async { let devices = devices() @@ -31,7 +62,7 @@ final class DeviceListViewModelTests: XCTestCase { let expectedDeviceNames2 = ["device1"] let foundDeviceNames2 = viewModel.filteredDevices.map(\.name) - XCTAssertEqual(expectedDeviceNames2, foundDeviceNames2) + XCTAssertEqual(foundDeviceNames2, expectedDeviceNames2) viewModel.searchText = "fake_name" XCTAssertTrue(viewModel.filteredDevices.isEmpty) @@ -50,10 +81,13 @@ final class DeviceListViewModelTests: XCTestCase { viewModel.searchText = "" - XCTAssertEqual(viewModel.filteredDevices.map(\.name), devices.map(\.name)) + let expectedDeviceNames = devices.map(\.name) + let foundDeviceNames = viewModel.filteredDevices.map(\.name) + + XCTAssertEqual(foundDeviceNames, expectedDeviceNames) } - func testIsFiltered_withSearchTextMatchingDevices_shouldReturnTrue() { + func testIsFiltered_withSearchTextMatchingDevices_shouldReturnTrue() async { let devices = devices() let viewModel = makeSUT( @@ -61,12 +95,15 @@ final class DeviceListViewModelTests: XCTestCase { currentDeviceId: mockCurrentDeviceId ) + let userDevices = await viewModel.fetchUserDevices() + await viewModel.arrangeDevices(userDevices) + viewModel.searchText = "dev" XCTAssertTrue(viewModel.isFiltered) } - func testIsFiltered_withEmptySearchText_shouldReturnFalse() { + func testIsFiltered_withEmptySearchText_shouldReturnFalse() async { let devices = devices() let viewModel = makeSUT( @@ -74,11 +111,75 @@ final class DeviceListViewModelTests: XCTestCase { currentDeviceId: mockCurrentDeviceId ) + let userDevices = await viewModel.fetchUserDevices() + await viewModel.arrangeDevices(userDevices) + viewModel.searchText = "" XCTAssertFalse(viewModel.isFiltered) } + func testStartAutoRefreshUserDevices_cancellation() async throws { + let devices = devices() + + let viewModel = makeSUT( + devices: devices, + currentDeviceId: mockCurrentDeviceId, + updateInterval: 5 + ) + + let userDevices = await viewModel.fetchUserDevices() + await viewModel.arrangeDevices(userDevices) + + let task = Task { + try await viewModel.startAutoRefreshUserDevices() + } + + task.cancel() + + XCTAssertTrue(task.isCancelled, "Task should be cancelled") + } + + func testActionsForDevice_selectedMobileDevice_returnsCorrectActions() async throws { + let devices = devices() + + let viewModel = makeSUT( + devices: devices, + currentDeviceId: mockCurrentDeviceId + ) + + let userDevices = await viewModel.fetchUserDevices() + await viewModel.arrangeDevices(userDevices) + + let selectedDevice = try XCTUnwrap(devices.first {$0.id == mockCurrentDeviceId}) + + let actions = viewModel.actionsForDevice(selectedDevice) + + let expectedActions: [DeviceCenterActionType] = [.cameraUploads, .rename, .info] + let actionsType = actions?.compactMap {$0.type} + XCTAssertEqual(actionsType, expectedActions, "Actions for the current device are incorrect") + } + + func testActionsForDevice_selectedOtherDevice_returnsCorrectActions() async throws { + let devices = devices() + + let viewModel = makeSUT( + devices: devices, + currentDeviceId: mockAuxDeviceId + ) + + let userDevices = await viewModel.fetchUserDevices() + await viewModel.arrangeDevices(userDevices) + + let selectedDevice = try XCTUnwrap(devices.first {$0.id == mockAuxDeviceId}) + + let actions = viewModel.actionsForDevice(selectedDevice) + + let expectedActions: [DeviceCenterActionType] = [.rename, .info] + let actionsType = actions?.compactMap {$0.type} + XCTAssertEqual(actionsType, expectedActions, "Actions for the current device are incorrect") + } + private func filteredDevices(by text: String) -> [DeviceEntity] { devices().filter { $0.name.lowercased().contains(text.lowercased()) @@ -86,9 +187,9 @@ final class DeviceListViewModelTests: XCTestCase { } private func devices() -> [DeviceEntity] { - var backup1 = BackupEntity(id: 1, name: "backup1") + var backup1 = BackupEntity(id: 1, name: "backup1", type: .cameraUpload) backup1.backupStatus = .upToDate - var backup2 = BackupEntity(id: 2, name: "backup2") + var backup2 = BackupEntity(id: 2, name: "backup2", type: .upSync) backup2.backupStatus = .upToDate let device1 = DeviceEntity( @@ -101,7 +202,7 @@ final class DeviceListViewModelTests: XCTestCase { ) let device2 = DeviceEntity( - id: "2", + id: mockAuxDeviceId, name: "device2", backups: [ backup2 @@ -116,10 +217,19 @@ final class DeviceListViewModelTests: XCTestCase { [.upToDate, .offline, .blocked, .outOfQuota, .error, .disabled, .paused, .updating, .scanning, .initialising, .backupStopped, .noCameraUploads] } - private func makeSUT(devices: [DeviceEntity], currentDeviceId: String, file: StaticString = #file, line: UInt = #line) -> DeviceListViewModel { + private func makeSUT( + devices: [DeviceEntity], + currentDeviceId: String, + updateInterval: UInt64 = 1, + file: StaticString = #file, + line: UInt = #line + ) -> DeviceListViewModel { + let backupStatusEntities = backupStatusEntities() let sut = DeviceListViewModel( + devicesUpdatePublisher: PassthroughSubject<[DeviceEntity], Never>(), + updateInterval: updateInterval, router: MockDeviceListViewRouter(), deviceCenterUseCase: MockDeviceCenterUseCase(devices: devices, currentDeviceId: currentDeviceId), deviceListAssets: @@ -138,9 +248,27 @@ final class DeviceListViewModelTests: XCTestCase { placeHolder: "", cancelTitle: "" ), - backupStatuses: backupStatusEntities.compactMap { BackupStatus(status: $0) } + backupStatuses: backupStatusEntities.compactMap { BackupStatus(status: $0) }, + deviceCenterActions: [ + DeviceCenterAction( + type: .cameraUploads + ), + DeviceCenterAction( + type: .info + ), + DeviceCenterAction( + type: .rename + ), + DeviceCenterAction( + type: .showInBackups + ), + DeviceCenterAction( + type: .showInCD + ) + ] ) + trackForMemoryLeaks(on: sut, file: file, line: line) return sut } } diff --git a/Modules/Presentation/Features/Settings/Package.swift b/Modules/Presentation/Features/Settings/Package.swift index 9f46d1fb86..4d8bf1e4b2 100644 --- a/Modules/Presentation/Features/Settings/Package.swift +++ b/Modules/Presentation/Features/Settings/Package.swift @@ -14,7 +14,7 @@ let package = Package( ], dependencies: [ .package(path: "../../../Domain/MEGADomain"), - .package(path: "../MEGAPresentation") + .package(path: "../../MEGAPresentation") ], targets: [ .target( diff --git a/Modules/Presentation/Features/Settings/Sources/Settings/About/model/APIEnvironmentChangingAlert.swift b/Modules/Presentation/Features/Settings/Sources/Settings/About/model/APIEnvironmentChangingAlert.swift index f7683f5bdb..d74c120ec8 100644 --- a/Modules/Presentation/Features/Settings/Sources/Settings/About/model/APIEnvironmentChangingAlert.swift +++ b/Modules/Presentation/Features/Settings/Sources/Settings/About/model/APIEnvironmentChangingAlert.swift @@ -1,4 +1,3 @@ - public struct APIEnvironmentChangingAlert { public var title: String public var message: String diff --git a/Modules/Presentation/Features/Settings/Sources/Settings/About/model/AppVersion.swift b/Modules/Presentation/Features/Settings/Sources/Settings/About/model/AppVersion.swift index 5ad4d00540..7897e06e76 100644 --- a/Modules/Presentation/Features/Settings/Sources/Settings/About/model/AppVersion.swift +++ b/Modules/Presentation/Features/Settings/Sources/Settings/About/model/AppVersion.swift @@ -1,4 +1,3 @@ - public struct AppVersion { public var title: String public var message: String diff --git a/Modules/Presentation/Features/Settings/Sources/Settings/About/model/ChangeSfuServerAlert.swift b/Modules/Presentation/Features/Settings/Sources/Settings/About/model/ChangeSfuServerAlert.swift index a58ac4d832..40fa2ba91a 100644 --- a/Modules/Presentation/Features/Settings/Sources/Settings/About/model/ChangeSfuServerAlert.swift +++ b/Modules/Presentation/Features/Settings/Sources/Settings/About/model/ChangeSfuServerAlert.swift @@ -1,4 +1,3 @@ - public struct ChangeSfuServerAlert { public var title: String public var message: String diff --git a/Modules/Presentation/Features/Settings/Sources/Settings/About/model/LogTogglingAlert.swift b/Modules/Presentation/Features/Settings/Sources/Settings/About/model/LogTogglingAlert.swift index 7ad3ae3d96..ab5fa7344f 100644 --- a/Modules/Presentation/Features/Settings/Sources/Settings/About/model/LogTogglingAlert.swift +++ b/Modules/Presentation/Features/Settings/Sources/Settings/About/model/LogTogglingAlert.swift @@ -1,4 +1,3 @@ - public struct LogTogglingAlert { public var enableTitle: String public var enableMessage: String diff --git a/Modules/Presentation/Features/Settings/Sources/Settings/TermsAndPolicies/TermsAndPoliciesView.swift b/Modules/Presentation/Features/Settings/Sources/Settings/TermsAndPolicies/TermsAndPoliciesView.swift index 6bbdb399df..cd8384e919 100644 --- a/Modules/Presentation/Features/Settings/Sources/Settings/TermsAndPolicies/TermsAndPoliciesView.swift +++ b/Modules/Presentation/Features/Settings/Sources/Settings/TermsAndPolicies/TermsAndPoliciesView.swift @@ -1,4 +1,3 @@ - import SwiftUI public struct TermsAndPoliciesView: View { diff --git a/Modules/Presentation/MEGAAssets/Package.swift b/Modules/Presentation/MEGAAssets/Package.swift index 95a302b840..643625916c 100644 --- a/Modules/Presentation/MEGAAssets/Package.swift +++ b/Modules/Presentation/MEGAAssets/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "MEGAAssets", platforms: [ @@ -17,10 +19,10 @@ let package = Package( .target( name: "MEGAAssets", dependencies: [], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")]), + swiftSettings: settings), .testTarget( name: "MEGAAssetsTests", dependencies: ["MEGAAssets"], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")]) + swiftSettings: settings) ] ) diff --git a/Modules/Presentation/MEGAAssets/Sources/MEGAAssets/MEGAAssets.swift b/Modules/Presentation/MEGAAssets/Sources/MEGAAssets/MEGAAssets.swift index 34b661eead..9235c244be 100644 --- a/Modules/Presentation/MEGAAssets/Sources/MEGAAssets/MEGAAssets.swift +++ b/Modules/Presentation/MEGAAssets/Sources/MEGAAssets/MEGAAssets.swift @@ -1,3 +1,11 @@ public struct MEGAAssets { public init() {} } + +import UIKit +public class MEGAAssetsImageProvider { + // for any image located in bundle where this class has built + public static func image(named: String) -> UIImage? { + return UIImage(named: named, in: Bundle(for: self), with: nil) + } +} diff --git a/Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/ActionSheetIcons/cloudDriveFolder.imageset/Contents.json b/Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/ActionSheetIcons/cloudDriveFolder.imageset/Contents.json new file mode 100644 index 0000000000..6210d90aff --- /dev/null +++ b/Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/ActionSheetIcons/cloudDriveFolder.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "filename" : "Group 1.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Group.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/ActionSheetIcons/cloudDriveFolder.imageset/Group 1.pdf b/Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/ActionSheetIcons/cloudDriveFolder.imageset/Group 1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..21781cf9aeaa06dd78d033cb1b86f37e358f4348 GIT binary patch literal 1797 zcmZvdU2oGc6o&8pE6&ZN4QYu#Vmp?mNvvZC0b+FAOU3 z`@}xyJ+JMPliTZ?b5>|V7*Obc{3HMu7jSt=Y`1cM3D0@?$##$969|M$H)|Mow%L;M z+WuL$cKQAa7I*o-b|AkAE9mq94TJMII(`>lwOc|9!L+J0t~?wJZATa@xOAa`^ALJV zc%cvB1~qWOIMdn8n26Itt6JAp639%5+@c6CKL*|;=Hn`e367^n(h?pON4Oy<{xCnU zLeiKZp{N7nrFl9i#(<(QM%N5g=|U)~sDM2cMr#VJE^1ygHuJW;4-NJUcZN0;wM{%!E`4qcpnB3d5{S>KVLsEuL|BheRpmCstB5BfA?IgGBf}=6 zpGS+bEO?zBv{&I+kxo zp?A49weM++Nflcf-t*Mz%C}wDj|1F)$JU2W`R(s73*~L|Xg&CBSL^1ge}VhdAJS!s z7Z7Xn7brwp+M$2mwRQm4T$euFqVGoAVMfWk>o4Bv~Xii{uI%V!~ATq8Xb_|2SXzva=6_f+qV4-7hF9g#^U@!EQ2*po>VL zPWME_c!eqs5Z>4WR8{EBe3ry!7SOnB)*HJc_@OF4T%#SQL%+3WaJpWd?M*3{eLsRL dcrv)WTmQ4De)?cFyKyRqNiE6A$-6H%{{eOebol@P literal 0 HcmV?d00001 diff --git a/Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/ActionSheetIcons/cloudDriveFolder.imageset/Group.pdf b/Modules/Presentation/MEGAAssets/Sources/MEGAAssets/Resources/Images.xcassets/ActionSheetIcons/cloudDriveFolder.imageset/Group.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a3e108e81c46f6f3d64fb316bd3bd4a6e9119bb9 GIT binary patch literal 1797 zcmZvdU2oGc6o&8pE6&ZN4QY-);y9M3NvvZC0b+FAOU3 z`@}xyJ+JMPliTZ?b51oO92oRJeiDF-3%I-_wp+QsMC82qWV^@l2_(X$n>CC(+iXd3 zZU3xWyL^8Ii@W?^JCNUmQ#L)o!r(lPj^D*s?UpcFb5pSzR~`Zrpavni;5wTb6A4CjRqMJ;0=Ws1TNL5t$H1G!eB26Rg5&9tv_wS35pD>IKg=)6 zkTfPpDC)p?X`T*>F`%Tz=$fM{O=ZO@CSgyd(V79TX)S8bC*QQ>S{gtTLYYxxVlCm4 zs*0hp*-W$*&rZ#xMCwNzGa;>{sWiIHsS(W70+5wuZMe*K;I;j0$y2t2j80q0U`sfo zMj9``Q%r?s;1SsuvUD5bRm78#kn=O8QDKwO z&!a_I7Q9X_wgNvW@q8cVh-Btim^L#kz~n+o5RHlAXKqEsnL3`qC-U%ybmS{>9m}_) z(7Rll+V?chRTWzr-t*Mzinm?Yj|1F)$JU2W@$K&~3&m~oXg&CBSL^1ge}VhdAJS!s z7Z7Xn7brwp+M$2mwRQm4T$euFqVGoAVMf KotlinInt { + KotlinInt(integerLiteral: self) + } +} diff --git a/Modules/Presentation/MEGAPresentation/Sources/MEGAPresentation/FeatureFlag/FeatureFlagKey.swift b/Modules/Presentation/MEGAPresentation/Sources/MEGAPresentation/FeatureFlag/FeatureFlagKey.swift index 0c6439b397..7e9b5361c9 100644 --- a/Modules/Presentation/MEGAPresentation/Sources/MEGAPresentation/FeatureFlag/FeatureFlagKey.swift +++ b/Modules/Presentation/MEGAPresentation/Sources/MEGAPresentation/FeatureFlag/FeatureFlagKey.swift @@ -4,4 +4,5 @@ public enum FeatureFlagKey: FeatureFlagName, CaseIterable { case albumShareLink = "Album Share Link" case deviceCenter = "Device Center" case waitingRoom = "Waiting Room" + case newHomeSearch = "New Home Search" } diff --git a/Modules/Repository/ChatRepo/Package.swift b/Modules/Repository/ChatRepo/Package.swift index 794641dfbc..9e4fcba65a 100644 --- a/Modules/Repository/ChatRepo/Package.swift +++ b/Modules/Repository/ChatRepo/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "ChatRepo", platforms: [ @@ -15,8 +17,8 @@ let package = Package( ], dependencies: [ .package(path: "../../Domain/MEGADomain"), - .package(path: "../../MEGAChatSdk"), - .package(path: "../../MEGASDKRepo") + .package(path: "../../DataSource/MEGAChatSdk"), + .package(path: "../MEGASDKRepo") ], targets: [ .target( @@ -26,7 +28,7 @@ let package = Package( "MEGAChatSdk", "MEGASDKRepo" ], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + swiftSettings: settings ) ] ) diff --git a/Modules/Repository/ChatRepo/Sources/ChatRepo/MEGAChatSDK/MEGAchatSDK/RequestDelegate/ChatRequestDelegate.swift b/Modules/Repository/ChatRepo/Sources/ChatRepo/MEGAChatSDK/MEGAchatSDK/RequestDelegate/ChatRequestDelegate.swift index 7c4ec00ccb..7a5f6fa7af 100644 --- a/Modules/Repository/ChatRepo/Sources/ChatRepo/MEGAChatSDK/MEGAchatSDK/RequestDelegate/ChatRequestDelegate.swift +++ b/Modules/Repository/ChatRepo/Sources/ChatRepo/MEGAChatSDK/MEGAchatSDK/RequestDelegate/ChatRequestDelegate.swift @@ -4,14 +4,16 @@ public typealias MEGAChatRequestCompletion = (_ result: Result) -> Void) { + if let chatRoom = chatSdk.chatRoom(forChatId: chatId) { + completion(.success(chatRoom.retentionTime)) + } else { + completion(.failure(.generic)) + } + } + + public func setChatRetentionTime(for chatId: ChatIdEntity, period: UInt, completion: @escaping (Result) -> Void) { + chatSdk.setChatRetentionTime(chatId, period: period, delegate: ChatRequestDelegate { result in + switch result { + case .success(let request): + let requestPeriod = UInt(truncatingIfNeeded: request.number) + completion(.success(requestPeriod)) + case .failure(let error): + completion(.failure(error.toManageChatHistoryErrorEntity())) + } + }) + } + + public func clearChatHistory(for chatId: ChatIdEntity, completion: @escaping (Result) -> Void) { + chatSdk.clearChatHistory(chatId, delegate: ChatRequestDelegate { request in + switch request { + case .success: + completion(.success(())) + case .failure(let error): + completion(.failure(error.toManageChatHistoryErrorEntity())) + } + }) + } +} + +private extension MEGAChatError { + func toManageChatHistoryErrorEntity() -> ManageChatHistoryErrorEntity { + switch type { + case .MEGAChatErrorTypeArgs: + return .chatIdInvalid + case .MEGAChatErrorTypeNoEnt: + return .chatIdDoesNotExist + case .MEGAChatErrorTypeAccess: + return .notEnoughPrivileges + default: + return .generic + } + } +} diff --git a/Modules/Repository/ChatRepo/Sources/ChatRepo/Repository/Chat/WaitingRoomRepository.swift b/Modules/Repository/ChatRepo/Sources/ChatRepo/Repository/Chat/WaitingRoomRepository.swift new file mode 100644 index 0000000000..41a2f39106 --- /dev/null +++ b/Modules/Repository/ChatRepo/Sources/ChatRepo/Repository/Chat/WaitingRoomRepository.swift @@ -0,0 +1,18 @@ +import MEGAChatSdk +import MEGADomain + +public final class WaitingRoomRepository: WaitingRoomRepositoryProtocol { + private let chatSdk: MEGAChatSdk + + public static var newRepo: WaitingRoomRepository { + WaitingRoomRepository(chatSdk: MEGAChatSdk.sharedChatSdk) + } + + public init(chatSdk: MEGAChatSdk) { + self.chatSdk = chatSdk + } + + public func userName() -> String { + chatSdk.myFullname ?? "" + } +} diff --git a/Modules/Repository/MEGAPickerFileProviderRepo/Package.swift b/Modules/Repository/MEGAPickerFileProviderRepo/Package.swift index 4ba06630b2..d804cb5b93 100644 --- a/Modules/Repository/MEGAPickerFileProviderRepo/Package.swift +++ b/Modules/Repository/MEGAPickerFileProviderRepo/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "MEGAPickerFileProviderRepo", platforms: [ @@ -15,18 +17,20 @@ let package = Package( ], dependencies: [ .package(path: "../../Domain/MEGAPickerFileProviderDomain"), - .package(path: "../../MEGASDKRepo"), + .package(path: "../MEGASDKRepo"), .package(url: "https://github.com/meganz/SAMKeychain.git", from: "2.0.0"), .package(url: "https://github.com/firebase/firebase-ios-sdk.git", from: "9.0.0") ], targets: [ .target( name: "MEGAPickerFileProviderRepo", - dependencies: ["MEGAPickerFileProviderDomain", "MEGASDKRepo"] + dependencies: ["MEGAPickerFileProviderDomain", "MEGASDKRepo"], + swiftSettings: settings ), .testTarget( name: "MEGAPickerFileProviderRepoTests", - dependencies: ["MEGAPickerFileProviderRepo"] + dependencies: ["MEGAPickerFileProviderRepo"], + swiftSettings: settings ) ] ) diff --git a/Modules/Repository/MEGARepo/Package.swift b/Modules/Repository/MEGARepo/Package.swift index 557430273a..700604ece5 100644 --- a/Modules/Repository/MEGARepo/Package.swift +++ b/Modules/Repository/MEGARepo/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "MEGARepo", platforms: [ @@ -20,7 +22,11 @@ let package = Package( targets: [ .target( name: "MEGARepo", - dependencies: ["MEGADomain"] - ) + dependencies: ["MEGADomain"], + swiftSettings: settings + ), + .testTarget( + name: "MEGARepoTests", + dependencies: ["MEGARepo"]) ] ) diff --git a/MEGAData/SDK/Model Mapping/Chat/Call/CameraPositionEntity+Mapper.swift b/Modules/Repository/MEGARepo/Sources/MEGARepo/Mappers/CameraPositionEntity+Mapper.swift similarity index 100% rename from MEGAData/SDK/Model Mapping/Chat/Call/CameraPositionEntity+Mapper.swift rename to Modules/Repository/MEGARepo/Sources/MEGARepo/Mappers/CameraPositionEntity+Mapper.swift diff --git a/MEGAData/Repository/PhotosLibrary/PhotosLibraryRepository.swift b/Modules/Repository/MEGARepo/Sources/MEGARepo/Photos/PhotosLibraryRepository.swift similarity index 67% rename from MEGAData/Repository/PhotosLibrary/PhotosLibraryRepository.swift rename to Modules/Repository/MEGARepo/Sources/MEGARepo/Photos/PhotosLibraryRepository.swift index ff49773339..89af9bfaf0 100644 --- a/MEGAData/Repository/PhotosLibrary/PhotosLibraryRepository.swift +++ b/Modules/Repository/MEGARepo/Sources/MEGARepo/Photos/PhotosLibraryRepository.swift @@ -1,21 +1,22 @@ import MEGADomain import Photos -struct PhotosLibraryRepository: PhotosLibraryRepositoryProtocol { - static var newRepo: PhotosLibraryRepository { +public struct PhotosLibraryRepository: PhotosLibraryRepositoryProtocol { + public static var newRepo: PhotosLibraryRepository { PhotosLibraryRepository() } - - var completion: ((SaveMediaToPhotosErrorEntity?) -> Void)? - let options: PHAssetResourceCreationOptions = { + + private let options: PHAssetResourceCreationOptions = { let options = PHAssetResourceCreationOptions() options.shouldMoveFile = true return options }() - - func copyMediaFileToPhotos(at url: URL, completion: ((SaveMediaToPhotosErrorEntity?) -> Void)?) { + + private let library = PHPhotoLibrary.shared() + + public func copyMediaFileToPhotos(at url: URL, completion: ((SaveMediaToPhotosErrorEntity?) -> Void)?) { let type: PHAssetResourceType = url.fileExtensionGroup.isImage ? .photo : .video - PHPhotoLibrary.shared().performChanges { + library.performChanges { PHAssetCreationRequest.forAsset().addResource(with: type, fileURL: url, options: self.options) } completionHandler: { success, _ in if success { diff --git a/MEGAData/Repository/AudioVideo/CaptureDeviceRepository.swift b/Modules/Repository/MEGARepo/Sources/MEGARepo/Repository/AudioVideo/CaptureDeviceRepository.swift similarity index 50% rename from MEGAData/Repository/AudioVideo/CaptureDeviceRepository.swift rename to Modules/Repository/MEGARepo/Sources/MEGARepo/Repository/AudioVideo/CaptureDeviceRepository.swift index 11f07384a8..d22ab918cd 100644 --- a/MEGAData/Repository/AudioVideo/CaptureDeviceRepository.swift +++ b/Modules/Repository/MEGARepo/Sources/MEGARepo/Repository/AudioVideo/CaptureDeviceRepository.swift @@ -1,13 +1,14 @@ +import AVFoundation import MEGADomain -struct CaptureDeviceRepository: CaptureDeviceRepositoryProtocol { - - func wideAngleCameraLocalizedName(postion: CameraPositionEntity) -> String? { - guard let capturePosition = AVCaptureDevice.Position(rawValue: postion.toCameraPositionCode()) else { +public struct CaptureDeviceRepository: CaptureDeviceRepositoryProtocol { + public init() {} + + public func wideAngleCameraLocalizedName(position: CameraPositionEntity) -> String? { + guard let capturePosition = AVCaptureDevice.Position(rawValue: position.toCameraPositionCode()) else { return nil } return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: capturePosition)?.localizedName } - } diff --git a/Modules/Repository/MEGARepo/Sources/MEGARepo/Repository/Contacts/ContactsRepository.swift b/Modules/Repository/MEGARepo/Sources/MEGARepo/Repository/Contacts/ContactsRepository.swift new file mode 100644 index 0000000000..179af5a18b --- /dev/null +++ b/Modules/Repository/MEGARepo/Sources/MEGARepo/Repository/Contacts/ContactsRepository.swift @@ -0,0 +1,38 @@ +import Contacts +import MEGADomain + +public struct ContactsRepository: ContactsRepositoryProtocol { + public static var newRepo: ContactsRepository { + ContactsRepository() + } + + public var isAuthorizedToAccessPhoneContacts: Bool { + CNContactStore.authorizationStatus(for: .contacts) == .authorized + } + + private let store = CNContactStore() + + public init() {} + + public func fetchContacts() -> [CNContact] { + var contacts: [CNContact] = [] + + let keys: [any CNKeyDescriptor] = [ + CNContactGivenNameKey as (any CNKeyDescriptor), + CNContactFamilyNameKey as (any CNKeyDescriptor), + CNContactEmailAddressesKey as (any CNKeyDescriptor) + ] + + let fetchRequest = CNContactFetchRequest(keysToFetch: keys) + + do { + try store.enumerateContacts(with: fetchRequest) { contact, _ in + contacts.append(contact) + } + } catch { + assertionFailure("[ContactsRepository] Unable to fetch contacts") + } + + return contacts + } +} diff --git a/Modules/Repository/MEGARepo/Sources/MEGARepo/Filesystem/FileSystemRepository.swift b/Modules/Repository/MEGARepo/Sources/MEGARepo/Repository/Filesystem/FileSystemRepository.swift similarity index 100% rename from Modules/Repository/MEGARepo/Sources/MEGARepo/Filesystem/FileSystemRepository.swift rename to Modules/Repository/MEGARepo/Sources/MEGARepo/Repository/Filesystem/FileSystemRepository.swift diff --git a/Modules/Repository/MEGARepo/Sources/MEGARepo/Preference/PreferenceRepository.swift b/Modules/Repository/MEGARepo/Sources/MEGARepo/Repository/Preference/PreferenceRepository.swift similarity index 100% rename from Modules/Repository/MEGARepo/Sources/MEGARepo/Preference/PreferenceRepository.swift rename to Modules/Repository/MEGARepo/Sources/MEGARepo/Repository/Preference/PreferenceRepository.swift diff --git a/Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/ContactsRepositoryTests.swift b/Modules/Repository/MEGARepo/Tests/MEGARepoTests/ContactsRepositoryTests.swift similarity index 95% rename from Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/ContactsRepositoryTests.swift rename to Modules/Repository/MEGARepo/Tests/MEGARepoTests/ContactsRepositoryTests.swift index d60329f47b..1da90ed644 100644 --- a/Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/ContactsRepositoryTests.swift +++ b/Modules/Repository/MEGARepo/Tests/MEGARepoTests/ContactsRepositoryTests.swift @@ -1,6 +1,5 @@ - import Contacts -import MEGASDKRepo +import MEGARepo import XCTest final class ContactsRepositoryTests: XCTestCase { diff --git a/Modules/Repository/MEGASDKRepo/.swiftpm/xcode/xcshareddata/xcschemes/MEGASDKRepo.xcscheme b/Modules/Repository/MEGASDKRepo/.swiftpm/xcode/xcshareddata/xcschemes/MEGASDKRepo.xcscheme new file mode 100644 index 0000000000..598abe1ebe --- /dev/null +++ b/Modules/Repository/MEGASDKRepo/.swiftpm/xcode/xcshareddata/xcschemes/MEGASDKRepo.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Repository/MEGASDKRepo/Package.swift b/Modules/Repository/MEGASDKRepo/Package.swift index dea33d9186..6697edb967 100644 --- a/Modules/Repository/MEGASDKRepo/Package.swift +++ b/Modules/Repository/MEGASDKRepo/Package.swift @@ -2,6 +2,8 @@ import PackageDescription +let settings: [SwiftSetting] = [.unsafeFlags(["-warnings-as-errors"]), .enableExperimentalFeature("ExistentialAny")] + let package = Package( name: "MEGASDKRepo", platforms: [ @@ -18,11 +20,11 @@ let package = Package( dependencies: [ .package(path: "../../Domain/MEGADomain"), .package(path: "../../Domain/MEGAAnalyticsDomain"), - .package(path: "../../MEGASdk"), + .package(path: "../../DataSource/MEGASdk"), .package(path: "../../Infrastracture/MEGATest"), .package(url: "https://github.com/meganz/SAMKeychain.git", from: "2.0.0"), .package(url: "https://github.com/firebase/firebase-ios-sdk.git", from: "9.0.0"), - .package(path: "../../MEGARepo") + .package(path: "../../Repository/MEGARepo") ], targets: [ .target( @@ -36,11 +38,11 @@ let package = Package( .product(name: "FirebaseAppDistribution-Beta", package: "firebase-ios-sdk"), "MEGARepo" ], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")]), + swiftSettings: settings), .target( name: "MEGASDKRepoMock", dependencies: ["MEGASDKRepo"], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + swiftSettings: settings ), .testTarget( name: "MEGASDKRepoTests", @@ -50,7 +52,7 @@ let package = Package( .product(name: "MEGADomainMock", package: "MEGADomain"), "MEGATest" ], - swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + swiftSettings: settings ) ] ) diff --git a/iMEGA/My Account/Upgrade Account/Data Model/AccountPlanEntity+Mapper.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Account/AccountPlanEntity+Mapper.swift similarity index 79% rename from iMEGA/My Account/Upgrade Account/Data Model/AccountPlanEntity+Mapper.swift rename to Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Account/AccountPlanEntity+Mapper.swift index 16e99a5885..22110ebc15 100644 --- a/iMEGA/My Account/Upgrade Account/Data Model/AccountPlanEntity+Mapper.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Account/AccountPlanEntity+Mapper.swift @@ -1,18 +1,19 @@ import MEGADomain +import MEGASdk +import StoreKit // MARK: - SKProduct extension SKProduct { - func toAccountPlanEntity(product: SKProduct, - storage: Int, - transfer: Int) -> AccountPlanEntity { - return AccountPlanEntity(product: product, - storageLimit: storage, - transferLimit: transfer) + public func toAccountPlanEntity(storage: Int = 0, + transfer: Int = 0) -> AccountPlanEntity { + AccountPlanEntity(product: self, + storageLimit: storage, + transferLimit: transfer) } } -// MARK: - AccountPlanTerm -fileprivate extension AccountPlanTermEntity { +// MARK: - SubscriptionCycleEntity +fileprivate extension SubscriptionCycleEntity { init(productIdentifier identifier: String) { switch identifier { case let id where id.contains("oneYear"): self = .yearly @@ -27,11 +28,10 @@ fileprivate extension AccountPlanEntity { init(product: SKProduct, storageLimit: Int, transferLimit: Int) { - self.init(productIdentifier: product.productIdentifier) - + let productIdentifier = product.productIdentifier - term = AccountPlanTermEntity(productIdentifier: productIdentifier) + subscriptionCycle = SubscriptionCycleEntity(productIdentifier: productIdentifier) let planType = planType(productIdentifier: productIdentifier) name = MEGAAccountDetails.string(for: planType) @@ -48,7 +48,7 @@ fileprivate extension AccountPlanEntity { storage = displayStringForGBValue(gbValue: storageLimit) transfer = displayStringForGBValue(gbValue: transferLimit) } - + private func planType(productIdentifier: String) -> MEGAAccountType { if productIdentifier.contains("pro1") { return .proI diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Account/UserAttributeEntity+Mapper.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Account/UserAttributeEntity+Mapper.swift index f8c543c990..9ab950eb3f 100644 --- a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Account/UserAttributeEntity+Mapper.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Account/UserAttributeEntity+Mapper.swift @@ -41,3 +41,41 @@ extension UserAttributeEntity { } } } + +extension UserAttributeEntity { + public func toMEGAUserAttribute() -> MEGAUserAttribute { + switch self { + case .avatar: return MEGAUserAttribute.avatar + case .firstName: return MEGAUserAttribute.firstname + case .lastName: return MEGAUserAttribute.lastname + case .authRing: return MEGAUserAttribute.authRing + case .lastInteraction: return MEGAUserAttribute.lastInteraction + case .eD25519PublicKey: return MEGAUserAttribute.ed25519PublicKey + case .cU25519PublicKey: return MEGAUserAttribute.cu25519PublicKey + case .keyring: return MEGAUserAttribute.keyring + case .sigRsaPublicKey: return MEGAUserAttribute.sigRsaPublicKey + case .sigCU255PublicKey: return MEGAUserAttribute.sigCU255PublicKey + case .language: return MEGAUserAttribute.language + case .pwdReminder: return MEGAUserAttribute.pwdReminder + case .disableVersions: return MEGAUserAttribute.disableVersions + case .contactLinkVerification: return MEGAUserAttribute.contactLinkVerification + case .richPreviews: return MEGAUserAttribute.richPreviews + case .rubbishTime: return MEGAUserAttribute.rubbishTime + case .lastPSA: return MEGAUserAttribute.lastPSA + case .storageState: return MEGAUserAttribute.storageState + case .geolocation: return MEGAUserAttribute.geolocation + case .cameraUploadsFolder: return MEGAUserAttribute.cameraUploadsFolder + case .myChatFilesFolder: return MEGAUserAttribute.myChatFilesFolder + case .pushSettings: return MEGAUserAttribute.pushSettings + case .alias: return MEGAUserAttribute.alias + case .deviceNames: return MEGAUserAttribute.deviceNames + case .backupsFolder: return MEGAUserAttribute.backupsFolder + case .cookieSettings: return MEGAUserAttribute.cookieSettings + case .jsonSyncConfigData: return MEGAUserAttribute.jsonSyncConfigData + case .drivesName: return MEGAUserAttribute.drivesName + case .noCallKit: return MEGAUserAttribute.noCallKit + case .appsPreferences: return MEGAUserAttribute.appsPreferences + case .contentConsumptionPreferences: return MEGAUserAttribute.contentConsumptionPreferences + } + } +} diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Ads/AdsFlagEntity+Mapper.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Ads/AdsFlagEntity+Mapper.swift new file mode 100644 index 0000000000..67a78828b7 --- /dev/null +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/Ads/AdsFlagEntity+Mapper.swift @@ -0,0 +1,16 @@ +import MEGADomain +import MEGASdk + +extension AdsFlagEntity { + public func toAdsFlag() -> AdsFlag { + switch self { + case .defaultAds: return .default + case .forceAds: return .forceAds + case .ignoreMega: return .ignoreMega + case .ignoreCountry: return .ignoreCountry + case .ignoreIP: return .ignoreIP + case .ignorePro: return .ignorePRO + case .ignoreRollout: return .ignoreRollout + } + } +} diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/User/ContactRequestEntity+Mapper.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/User/ContactRequestEntity+Mapper.swift index ccc104a2b2..d98c088f15 100644 --- a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/User/ContactRequestEntity+Mapper.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Mappers/User/ContactRequestEntity+Mapper.swift @@ -3,8 +3,8 @@ import MEGASdk extension MEGAContactRequestList { public func toContactRequestEntities() -> [ContactRequestEntity] { - guard let size, size.intValue > 0 else { return [] } - return (0.. 0 else { return [] } + return (0.. [String: String] { + return try await withAsyncThrowingValue(in: { completion in + let flag = adsFlag.toAdsFlag() + let adsSlots = sdk.megaStringList(for: adUnits.map { $0.rawValue }) + + sdk.fetchAds(flag, adUnits: adsSlots, publicHandle: publicHandle, delegate: RequestDelegate { result in + switch result { + case .success(let request): + completion(.success(request.megaStringDictionary)) + case .failure(let error): + print(error) + completion(.failure(error)) + } + }) + }) + } + + public func queryAds(adsFlag: AdsFlagEntity, publicHandle: HandleEntity) async throws -> Int { + try await withAsyncThrowingValue(in: { completion in + let flag = adsFlag.toAdsFlag() + sdk.queryAds(flag, publicHandle: publicHandle, delegate: RequestDelegate { result in + switch result { + case .success(let request): + completion(.success(request.numDetails)) + case .failure(let error): + print(error) + completion(.failure(error)) + } + }) + }) + } +} diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Banner/BannerRepository.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Banner/BannerRepository.swift new file mode 100644 index 0000000000..3aaa64b49d --- /dev/null +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Banner/BannerRepository.swift @@ -0,0 +1,51 @@ +import MEGADomain +import MEGASdk + +public struct BannerRepository: BannerRepositoryProtocol { + public static var newRepo: BannerRepository { + BannerRepository(sdk: MEGASdk.sharedSdk) + } + + let sdk: MEGASdk + + // MARK: - BannerRepositoryProtocol + + public func banners(completion: @escaping (Result<[BannerEntity], BannerErrorEntity>) -> Void) { + + func mapError(sdkError: MEGAErrorType) -> BannerErrorEntity { + switch sdkError { + case .apiEAccess: return .userSessionTimeout + case .apiEInternal: return .internal + case .apiENoent: return .resourceDoesNotExist + default: return .unexpected + } + } + + func mapValue(request: MEGARequest) -> [BannerEntity] { + request.bannerList.bannerEntities + } + + sdk.getBanners(RequestDelegate { result in + switch result { + case .failure(let error): + completion(.failure(mapError(sdkError: error.type))) + case .success(let request): + completion(.success(request.bannerList.bannerEntities)) + } + }) + } + + public func dismissBanner( + withBannerId bannerId: Int, + completion: ((Result) -> Void)? + ) { + sdk.dismissBanner(bannerId, delegate: RequestDelegate { result in + switch result { + case .failure: + completion?(.failure(.unexpected)) + case .success: + completion?(.success(())) + } + }) + } +} diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Contact/ContactsRepository.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Contact/ContactsRepository.swift deleted file mode 100644 index d27884dafa..0000000000 --- a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Contact/ContactsRepository.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Contacts -import MEGADomain - -public struct ContactsRepository: ContactsRepositoryProtocol { - public var isAuthorizedToAccessPhoneContacts: Bool { - CNContactStore.authorizationStatus(for: .contacts) == .authorized - } - - public init() {} -} diff --git a/MEGAData/Repository/Favourites/FavouriteNodesRepository.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Favourites/FavouriteNodesRepository.swift similarity index 78% rename from MEGAData/Repository/Favourites/FavouriteNodesRepository.swift rename to Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Favourites/FavouriteNodesRepository.swift index 6580dd0b0b..ac71036eb2 100644 --- a/MEGAData/Repository/Favourites/FavouriteNodesRepository.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Favourites/FavouriteNodesRepository.swift @@ -1,10 +1,10 @@ import Foundation import MEGADomain -import MEGASDKRepo +import MEGASdk -final class FavouriteNodesRepository: NSObject, FavouriteNodesRepositoryProtocol { - static var newRepo: FavouriteNodesRepository { - FavouriteNodesRepository(sdk: MEGASdkManager.sharedMEGASdk()) +public final class FavouriteNodesRepository: NSObject, FavouriteNodesRepositoryProtocol { + public static var newRepo: FavouriteNodesRepository { + FavouriteNodesRepository(sdk: MEGASdk.sharedSdk) } private let sdk: MEGASdk @@ -16,11 +16,11 @@ final class FavouriteNodesRepository: NSObject, FavouriteNodesRepositoryProtocol } @available(*, renamed: "allFavouritesNodes()") - func getAllFavouriteNodes(completion: @escaping (Result<[NodeEntity], GetFavouriteNodesErrorEntity>) -> Void) { + public func getAllFavouriteNodes(completion: @escaping (Result<[NodeEntity], GetFavouriteNodesErrorEntity>) -> Void) { getFavouriteNodes(limitCount: 0, completion: completion) } - func allFavouritesNodes() async throws -> [NodeEntity] { + public func allFavouritesNodes() async throws -> [NodeEntity] { return try await withCheckedThrowingContinuation { continuation in getFavouriteNodes(limitCount: 0) { result in guard Task.isCancelled == false else { continuation.resume(throwing: GetFavouriteNodesErrorEntity.generic); return } @@ -30,7 +30,7 @@ final class FavouriteNodesRepository: NSObject, FavouriteNodesRepositoryProtocol } } - func getFavouriteNodes(limitCount: Int, completion: @escaping (Result<[NodeEntity], GetFavouriteNodesErrorEntity>) -> Void) { + public func getFavouriteNodes(limitCount: Int, completion: @escaping (Result<[NodeEntity], GetFavouriteNodesErrorEntity>) -> Void) { sdk.favourites(forParent: nil, count: limitCount, delegate: RequestDelegate { (result) in switch result { case .success(let request): @@ -54,7 +54,7 @@ final class FavouriteNodesRepository: NSObject, FavouriteNodesRepositoryProtocol }) } - func allFavouriteNodes(searchString: String?, completion: @escaping (Result<[NodeEntity], GetFavouriteNodesErrorEntity>) -> Void) { + public func allFavouriteNodes(searchString: String?, completion: @escaping (Result<[NodeEntity], GetFavouriteNodesErrorEntity>) -> Void) { getFavouriteNodes(limitCount: 0) { result in switch result { case .success(let nodes): @@ -69,13 +69,13 @@ final class FavouriteNodesRepository: NSObject, FavouriteNodesRepositoryProtocol } } - func registerOnNodesUpdate(callback: @escaping ([NodeEntity]) -> Void) { + public func registerOnNodesUpdate(callback: @escaping ([NodeEntity]) -> Void) { sdk.add(self) onNodesUpdate = callback } - func unregisterOnNodesUpdate() { + public func unregisterOnNodesUpdate() { sdk.remove(self) onNodesUpdate = nil @@ -83,7 +83,7 @@ final class FavouriteNodesRepository: NSObject, FavouriteNodesRepositoryProtocol } extension FavouriteNodesRepository: MEGAGlobalDelegate { - func onNodesUpdate(_ api: MEGASdk, nodeList: MEGANodeList?) { + public func onNodesUpdate(_ api: MEGASdk, nodeList: MEGANodeList?) { guard let nodesUpdateArray = nodeList?.toNodeArray() else { return } var shouldProcessOnNodesUpdate: Bool = false diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Network/NetworkMonitorRepository.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Network/NetworkMonitorRepository.swift index 183e84ece7..938e929684 100644 --- a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Network/NetworkMonitorRepository.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Network/NetworkMonitorRepository.swift @@ -1,4 +1,3 @@ - import MEGADomain import Network diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeActionRepository.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeActionRepository.swift new file mode 100644 index 0000000000..488e32e1da --- /dev/null +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeActionRepository.swift @@ -0,0 +1,238 @@ +import MEGADomain +import MEGASdk +import MEGASwift + +public struct NodeActionRepository: NodeActionRepositoryProtocol { + public static var newRepo: NodeActionRepository { + NodeActionRepository(sdk: MEGASdk.sharedSdk) + } + + private let sdk: MEGASdk + + public init(sdk: MEGASdk) { + self.sdk = sdk + } + + public func fetchNodes() async throws { + try await withAsyncThrowingValue { completion in + sdk.fetchNodes(with: RequestDelegate { result in + switch result { + case .success: + completion(.success(())) + case .failure: + completion(.failure(GenericErrorEntity())) + } + }) + } + } + + public func createFolder(name: String, parent: NodeEntity) async throws -> NodeEntity { + try await withAsyncThrowingValue { completion in + guard let parentNode = sdk.node(forHandle: parent.handle) else { + completion(.failure(CreateFolderErrorEntity.generic)) + return + } + + sdk.createFolder(withName: name, parent: parentNode, delegate: RequestDelegate { result in + switch result { + case .success(let request): + guard let node = sdk.node(forHandle: request.nodeHandle) else { + completion(.failure(CreateFolderErrorEntity.generic)) + return + } + + completion(.success(node.toNodeEntity())) + case .failure(let error): + if error.type == .apiEBusinessPastDue { + completion(.failure(CreateFolderErrorEntity.businessExpired)) + } else { + completion(.failure(CreateFolderErrorEntity.generic)) + } + } + }) + } + } + + public func rename(node: NodeEntity, name: String) async throws -> NodeEntity { + try await withAsyncThrowingValue { completion in + guard let megaNode = sdk.node(forHandle: node.handle) else { + completion(.failure(RenameNodeErrorEntity.generic)) + return + } + + sdk.renameNode(megaNode, newName: name, delegate: RequestDelegate { result in + switch result { + case .success(let request): + guard let node = sdk.node(forHandle: request.nodeHandle) else { + completion(.failure(RenameNodeErrorEntity.generic)) + return + } + + completion(.success(node.toNodeEntity())) + case .failure(let error): + if error.type == .apiEBusinessPastDue { + completion(.failure(RenameNodeErrorEntity.businessExpired)) + } else { + completion(.failure(RenameNodeErrorEntity.generic)) + } + } + }) + } + } + + public func trash(node: NodeEntity) async throws -> NodeEntity { + try await withAsyncThrowingValue { completion in + guard + let node = sdk.node(forHandle: node.handle), + let rubbishBinNode = sdk.rubbishNode + else { + completion(.failure(MoveNodeErrorEntity.generic)) + return + } + + sdk.move(node, newParent: rubbishBinNode, delegate: RequestDelegate { result in + switch result { + case .success(let request): + guard let node = sdk.node(forHandle: request.nodeHandle) else { + completion(.failure(MoveNodeErrorEntity.generic)) + return + } + completion(.success(node.toNodeEntity())) + case .failure(let error): + if error.type == .apiEBusinessPastDue { + completion(.failure(MoveNodeErrorEntity.businessExpired)) + } else { + completion(.failure(MoveNodeErrorEntity.generic)) + } + } + }) + } + } + + public func untrash(node: NodeEntity) async throws -> NodeEntity { + try await withAsyncThrowingValue { completion in + guard + let node = sdk.node(forHandle: node.handle), + sdk.isNode(inRubbish: node), + let restoreNode = sdk.node(forHandle: node.restoreHandle), + !sdk.isNode(inRubbish: restoreNode) + else { + completion(.failure(MoveNodeErrorEntity.generic)) + return + } + + sdk.move(node, newParent: restoreNode, delegate: RequestDelegate { result in + switch result { + case .success(let request): + guard let node = sdk.node(forHandle: request.nodeHandle) else { + completion(.failure(MoveNodeErrorEntity.generic)) + return + } + + completion(.success(node.toNodeEntity())) + case .failure(let error): + if error.type == .apiEBusinessPastDue { + completion(.failure(MoveNodeErrorEntity.businessExpired)) + } else { + completion(.failure(MoveNodeErrorEntity.generic)) + } + } + }) + } + } + + public func delete(node: NodeEntity) async throws { + try await withAsyncThrowingValue { (completion: @escaping (Result) -> Void) in + guard + let node = sdk.node(forHandle: node.handle), + sdk.isNode(inRubbish: node) + else { + completion(.failure(RemoveNodeErrorEntity.generic)) + return + } + + sdk.remove(node, delegate: RequestDelegate { result in + switch result { + case .success: + completion(.success(())) + case .failure(let error): + if error.type == .apiEMasterOnly { + completion(.failure(RemoveNodeErrorEntity.masterOnly)) + } else { + completion(.failure(RemoveNodeErrorEntity.generic)) + } + } + }) + } + } + + public func move(node: NodeEntity, toParent: NodeEntity) async throws -> NodeEntity { + try await withAsyncThrowingValue { completion in + guard + let node = sdk.node(forHandle: node.handle), + let parent = sdk.node(forHandle: toParent.handle) + else { + completion(.failure(MoveNodeErrorEntity.generic)) + return + } + + sdk.move(node, newParent: parent, delegate: RequestDelegate { result in + switch result { + case .success(let request): + guard let node = sdk.node(forHandle: request.nodeHandle) else { + completion(.failure(MoveNodeErrorEntity.generic)) + return + } + completion(.success(node.toNodeEntity())) + case .failure(let error): + if error.type == .apiEBusinessPastDue { + completion(.failure(MoveNodeErrorEntity.businessExpired)) + } else { + completion(.failure(MoveNodeErrorEntity.generic)) + } + } + }) + } + } + + public func removeLink(nodes: [NodeEntity]) async throws { + try await withThrowingTaskGroup(of: Void.self) { taskGroup in + guard taskGroup.isCancelled == false else { + throw CancellationError() + } + + nodes.forEach { node in + taskGroup.addTask { + try await removeLink(for: node) + } + } + + try await taskGroup.waitForAll() + } + } + + private func removeLink(for node: NodeEntity) async throws { + try await withAsyncThrowingValue { (completion: @escaping (Result) -> Void) in + guard let megaNode = node.toMEGANode(in: sdk) else { + completion(.failure(RemoveLinkErrorEntity.generic)) + return + } + + sdk.disableExport(megaNode, delegate: RequestDelegate { result in + switch result { + case .failure(let error): + switch error.type { + case .apiEBusinessPastDue: + completion(.failure(RemoveLinkErrorEntity.businessExpired)) + case .apiENoent: + completion(.failure(RemoveLinkErrorEntity.notFound)) + default: + completion(.failure(RemoveLinkErrorEntity.generic)) + } + case .success: + completion(.success(())) + } + }) + } + } +} diff --git a/MEGAData/Repository/Node/NodeAttributeRepository.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeAttributeRepository.swift similarity index 57% rename from MEGAData/Repository/Node/NodeAttributeRepository.swift rename to Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeAttributeRepository.swift index 38941bc6be..1791d62729 100644 --- a/MEGAData/Repository/Node/NodeAttributeRepository.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeAttributeRepository.swift @@ -1,31 +1,32 @@ import MEGADomain +import MEGASdk -struct NodeAttributeRepository: NodeAttributeRepositoryProtocol { - static var newRepo: NodeAttributeRepository { - NodeAttributeRepository(sdk: MEGASdk.shared) +public struct NodeAttributeRepository: NodeAttributeRepositoryProtocol { + public static var newRepo: NodeAttributeRepository { + NodeAttributeRepository(sdk: MEGASdk.sharedSdk) } - + private let sdk: MEGASdk - - init(sdk: MEGASdk) { + + public init(sdk: MEGASdk) { self.sdk = sdk } - - func pathFor(node: NodeEntity) -> String? { + + public func pathFor(node: NodeEntity) -> String? { guard let megaNode = sdk.node(forHandle: node.handle) else { return nil } return sdk.nodePath(for: megaNode) } - - func numberChildrenFor(node: NodeEntity) -> Int { + + public func numberChildrenFor(node: NodeEntity) -> Int { guard let megaNode = sdk.node(forHandle: node.handle) else { return 0 } return sdk.numberChildren(forParent: megaNode) } - - func isInRubbishBin(node: NodeEntity) -> Bool { + + public func isInRubbishBin(node: NodeEntity) -> Bool { guard let megaNode = sdk.node(forHandle: node.handle) else { return false } diff --git a/MEGAData/Repository/Node/NodeFavouriteActionRepository.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeFavouriteActionRepository.swift similarity index 83% rename from MEGAData/Repository/Node/NodeFavouriteActionRepository.swift rename to Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeFavouriteActionRepository.swift index 0d91e8e31f..729e2abdb6 100644 --- a/MEGAData/Repository/Node/NodeFavouriteActionRepository.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeFavouriteActionRepository.swift @@ -1,16 +1,19 @@ import Foundation import MEGADomain -import MEGASDKRepo +import MEGASdk -struct NodeFavouriteActionRepository: NodeFavouriteActionRepositoryProtocol { +public struct NodeFavouriteActionRepository: NodeFavouriteActionRepositoryProtocol { + public static var newRepo: NodeFavouriteActionRepository = { + NodeFavouriteActionRepository(sdk: MEGASdk.sharedSdk) + }() private let sdk: MEGASdk - init(sdk: MEGASdk) { + public init(sdk: MEGASdk) { self.sdk = sdk } - func favourite(node: NodeEntity) async throws { + public func favourite(node: NodeEntity) async throws { guard let node = sdk.node(forHandle: node.handle) else { throw NodeFavouriteErrorEntity.nodeNotFound } @@ -34,7 +37,7 @@ struct NodeFavouriteActionRepository: NodeFavouriteActionRepositoryProtocol { } } - func unFavourite(node: NodeEntity) async throws { + public func unFavourite(node: NodeEntity) async throws { guard let node = sdk.node(forHandle: node.handle) else { throw NodeFavouriteErrorEntity.nodeNotFound } diff --git a/MEGAData/Repository/Node/NodeUpdateRepository.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeUpdateRepository.swift similarity index 68% rename from MEGAData/Repository/Node/NodeUpdateRepository.swift rename to Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeUpdateRepository.swift index 25b3089428..c7c95757a8 100644 --- a/MEGAData/Repository/Node/NodeUpdateRepository.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/NodeUpdateRepository.swift @@ -1,18 +1,20 @@ import MEGADomain +import MEGASdk -struct NodeUpdateRepository: NodeUpdateRepositoryProtocol { - static var newRepo: NodeUpdateRepository { - NodeUpdateRepository(sdk: MEGASdkManager.sharedMEGASdk()) +public struct NodeUpdateRepository: NodeUpdateRepositoryProtocol { + public static var newRepo: NodeUpdateRepository { + NodeUpdateRepository(sdk: MEGASdk.sharedSdk) } private let sdk: MEGASdk - - init(sdk: MEGASdk) { + + public init(sdk: MEGASdk) { self.sdk = sdk } - func shouldProcessOnNodesUpdate(parentNode: NodeEntity, childNodes: [NodeEntity], - updatedNodes: [NodeEntity]) -> Bool { + public func shouldProcessOnNodesUpdate(parentNode: NodeEntity, + childNodes: [NodeEntity], + updatedNodes: [NodeEntity]) -> Bool { guard !updatedNodes.contains(where: { $0.parentHandle == parentNode.handle }) else { return true } let childNodesBase64Handles = Set(childNodes.compactMap({ $0.base64Handle })) diff --git a/MEGAData/Repository/Node/RubbishBinRepository.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/RubbishBinRepository.swift similarity index 73% rename from MEGAData/Repository/Node/RubbishBinRepository.swift rename to Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/RubbishBinRepository.swift index 88cb184ea5..819a83ba65 100644 --- a/MEGAData/Repository/Node/RubbishBinRepository.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/Node/RubbishBinRepository.swift @@ -1,39 +1,38 @@ +import MEGASdk import MEGADomain public struct RubbishBinRepository: RubbishBinRepositoryProtocol { - private let sdk: MEGASdk - private let nodeValidationRepository: any NodeValidationRepositoryProtocol - public static var newRepo: RubbishBinRepository { - RubbishBinRepository(sdk: MEGASdkManager.sharedMEGASdk(), nodeValidationRepository: NodeValidationRepository.newRepo) + RubbishBinRepository(sdk: MEGASdk.sharedSdk) } - + + private let sdk: MEGASdk + private enum Constants { static let syncDebrisFolderName = "SyncDebris" static let syncDebrisNodePath = "//bin/SyncDebris" } - - public init(sdk: MEGASdk, nodeValidationRepository: any NodeValidationRepositoryProtocol) { + + public init(sdk: MEGASdk) { self.sdk = sdk - self.nodeValidationRepository = nodeValidationRepository } - + public func isSyncDebrisNode(_ node: NodeEntity) -> Bool { guard let syncDebrisNodes = syncDebrisNodes(), syncDebrisNodes.isNotEmpty else { return false } - + return isSyncDebrisChild(node) } - + private func isSyncDebrisChild(_ node: NodeEntity) -> Bool { guard let megaNode = node.toMEGANode(in: sdk), let path = sdk.nodePath(for: megaNode) else { return false } - + return path.hasPrefix(Constants.syncDebrisNodePath) } - + private func syncDebrisNodes() -> [NodeEntity]? { guard let rubbishNode = sdk.rubbishNode else { return nil } - + return sdk.children(forParent: rubbishNode) .toNodeArray() .filter { diff --git a/MEGAData/Repository/User/UserAttributeRepository.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/User/UserAttributeRepository.swift similarity index 77% rename from MEGAData/Repository/User/UserAttributeRepository.swift rename to Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/User/UserAttributeRepository.swift index 8c325927fa..09b4b0afe0 100644 --- a/MEGAData/Repository/User/UserAttributeRepository.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/User/UserAttributeRepository.swift @@ -1,25 +1,25 @@ import MEGADomain -import MEGASDKRepo +import MEGASdk import MEGASwift -struct UserAttributeRepository: UserAttributeRepositoryProtocol { - static var newRepo: UserAttributeRepository { - UserAttributeRepository(sdk: MEGASdk.shared) +public struct UserAttributeRepository: UserAttributeRepositoryProtocol { + public static var newRepo: UserAttributeRepository { + UserAttributeRepository(sdk: MEGASdk.sharedSdk) } - + private let sdk: MEGASdk - + init(sdk: MEGASdk) { self.sdk = sdk } - - func updateUserAttribute(_ attribute: UserAttributeEntity, value: String) async throws { + + public func updateUserAttribute(_ attribute: UserAttributeEntity, value: String) async throws { return try await withCheckedThrowingContinuation { continuation in guard Task.isCancelled == false else { continuation.resume(throwing: CancellationError()) return } - + sdk.setUserAttributeType(attribute.toMEGAUserAttribute(), value: value, delegate: RequestDelegate { result in guard Task.isCancelled == false else { continuation.resume(throwing: CancellationError()) @@ -29,14 +29,14 @@ struct UserAttributeRepository: UserAttributeRepositoryProtocol { }) } } - - func updateUserAttribute(_ attribute: UserAttributeEntity, key: String, value: String) async throws { + + public func updateUserAttribute(_ attribute: UserAttributeEntity, key: String, value: String) async throws { return try await withCheckedThrowingContinuation { continuation in guard Task.isCancelled == false else { continuation.resume(throwing: CancellationError()) return } - + sdk.setUserAttributeType(attribute.toMEGAUserAttribute(), key: key, value: value, delegate: RequestDelegate { result in guard Task.isCancelled == false else { continuation.resume(throwing: CancellationError()) @@ -46,8 +46,8 @@ struct UserAttributeRepository: UserAttributeRepositoryProtocol { }) } } - - func userAttribute(for attribute: UserAttributeEntity) async throws -> [String: String]? { + + public func userAttribute(for attribute: UserAttributeEntity) async throws -> [String: String]? { try await withAsyncThrowingValue(in: { completion in sdk.getUserAttributeType(attribute.toMEGAUserAttribute(), delegate: RequestDelegate { result in switch result { diff --git a/MEGAData/Repository/User/UserImageRepository.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/User/UserImageRepository.swift similarity index 69% rename from MEGAData/Repository/User/UserImageRepository.swift rename to Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/User/UserImageRepository.swift index ffd0d6f535..b73780bf15 100644 --- a/MEGAData/Repository/User/UserImageRepository.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/Repository/User/UserImageRepository.swift @@ -1,61 +1,63 @@ import Combine import MEGADomain +import MEGASdk -struct UserImageRepository: UserImageRepositoryProtocol { - static var newRepo: UserImageRepository { - UserImageRepository(sdk: MEGASdkManager.sharedMEGASdk()) +public struct UserImageRepository: UserImageRepositoryProtocol { + public static var newRepo: UserImageRepository { + UserImageRepository(sdk: MEGASdk.sharedSdk) } - + private let sdk: MEGASdk private var userAvatarChangeSubscriber: UserAvatarChangeSubscriber? - - init(sdk: MEGASdk) { + + public init(sdk: MEGASdk) { self.sdk = sdk } - - func loadUserImage(withUserHandle handle: String?, - destinationPath: String, - completion: @escaping (Result) -> Void) { - - let thumbnailRequestDelegate = MEGAGetThumbnailRequestDelegate { request in - if let filePath = request.file, let image = UIImage(contentsOfFile: filePath) { - completion(.success(image)) - } else { + + public func loadUserImage(withUserHandle handle: String?, + destinationPath: String, + completion: @escaping (Result) -> Void) { + + let thumbnailRequestDelegate = RequestDelegate { result in + guard case .success(let request) = result else { completion(.failure(.unableToFetch)) + return } + + completion(.success(request.file)) } - + sdk.getAvatarUser(withEmailOrHandle: handle, destinationFilePath: destinationPath, delegate: thumbnailRequestDelegate) } - - func avatar(forUserHandle handle: String?, destinationPath: String) async throws -> UIImage { + + public func avatar(forUserHandle handle: String?, destinationPath: String) async throws -> ImageFilePathEntity { try await withCheckedThrowingContinuation { continuation in let thumbnailRequestDelegate = AvatarRequestDelegate { request in - if let filePath = request.file, let image = UIImage(contentsOfFile: filePath) { + if let filePath = request.file { guard Task.isCancelled == false else { continuation.resume(throwing: CancellationError()) return } - continuation.resume(returning: image) + continuation.resume(returning: filePath) } else { continuation.resume(throwing: UserImageLoadErrorEntity.unableToFetch) } } - + sdk.getAvatarUser(withEmailOrHandle: handle, destinationFilePath: destinationPath, delegate: thumbnailRequestDelegate, queueType: .globalBackground) } } - - func avatarColorHex(forBase64UserHandle handle: Base64HandleEntity) -> String? { + + public func avatarColorHex(forBase64UserHandle handle: Base64HandleEntity) -> String? { MEGASdk.avatarColor(forBase64UserHandle: handle) } - - mutating func requestAvatarChangeNotification(forUserHandles handles: [HandleEntity]) -> AnyPublisher<[HandleEntity], Never> { + + public mutating func requestAvatarChangeNotification(forUserHandles handles: [HandleEntity]) -> AnyPublisher<[HandleEntity], Never> { let userAvatarChangeSubscriber = UserAvatarChangeSubscriber(sdk: sdk, handles: handles) self.userAvatarChangeSubscriber = userAvatarChangeSubscriber return userAvatarChangeSubscriber.monitor @@ -64,12 +66,12 @@ struct UserImageRepository: UserImageRepositoryProtocol { private final class AvatarRequestDelegate: NSObject, MEGARequestDelegate { private let completion: (MEGARequest) -> Void - + init(completion: @escaping (MEGARequest) -> Void) { self.completion = completion super.init() } - + func onRequestFinish(_ api: MEGASdk, request: MEGARequest, error: MEGAError) { completion(request) } @@ -78,20 +80,20 @@ private final class AvatarRequestDelegate: NSObject, MEGARequestDelegate { private final class UserAvatarChangeSubscriber: NSObject, MEGAGlobalDelegate { private let handles: [HandleEntity] private let source: PassthroughSubject<[HandleEntity], Never> - + var monitor: AnyPublisher<[HandleEntity], Never> { source.eraseToAnyPublisher() } - + init(sdk: MEGASdk, handles: [HandleEntity]) { self.handles = handles self.source = PassthroughSubject<[HandleEntity], Never>() - + super.init() - + sdk.add(self, queueType: .globalBackground) } - + func onUsersUpdate(_ api: MEGASdk, userList: MEGAUserList) { guard let userListSize = userList.size else { return } let users = (0.. BannerEntity? in guard let banner = banner(at: index) else { return nil } return banner.bannerEntity diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/SDK/RequestDelegate/RequestDelegate.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/SDK/RequestDelegate/RequestDelegate.swift index f7b9cc4376..35949b517d 100644 --- a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/SDK/RequestDelegate/RequestDelegate.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepo/SDK/RequestDelegate/RequestDelegate.swift @@ -5,13 +5,16 @@ public typealias MEGARequestCompletion = (_ result: Result MEGANode? { - nodes.first { $0.handle == handle } + nodeForHandleCallCount += 1 + return nodes.first { $0.handle == handle } } } diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockContactRequestList.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockContactRequestList.swift index dff5233aa8..b126fcb186 100644 --- a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockContactRequestList.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockContactRequestList.swift @@ -7,9 +7,9 @@ public final class MockContactRequestList: MEGAContactRequestList { self.contactRequests = contactRequests } - public override var size: NSNumber! { NSNumber(value: contactRequests.count) } + public override var size: NSInteger { contactRequests.count } - public override func contactRequest(at index: Int) -> MEGAContactRequest! { - contactRequests[index] + public override func contactRequest(at index: Int) -> MEGAContactRequest? { + contactRequests[safe: index] } } diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockNode.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockNode.swift index fe66797009..b582069030 100644 --- a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockNode.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockNode.swift @@ -85,4 +85,6 @@ public final class MockNode: MEGANode { public override var fingerprint: String? { _fingerprint } public override func hasPreview() -> Bool { _hasPreview } + + public override var base64Handle: String? { String(handle) } } diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockRequest.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockRequest.swift index 4dd1f97afd..382c03fec7 100644 --- a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockRequest.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockRequest.swift @@ -15,6 +15,7 @@ public final class MockRequest: MEGARequest { private let stringDict: [String: String] private let _file: String? private let _accountDetails: MEGAAccountDetails? + private let _numDetails: Int public init(handle: MEGAHandle, set: MEGASet? = nil, @@ -28,7 +29,8 @@ public final class MockRequest: MEGARequest { backupInfoList: [MEGABackupInfo] = [], stringDict: [String: String] = [:], file: String? = nil, - accountDetails: MEGAAccountDetails? = nil) { + accountDetails: MEGAAccountDetails? = nil, + numDetails: Int = 0) { self.handle = handle _set = set _text = text @@ -42,6 +44,7 @@ public final class MockRequest: MEGARequest { self.stringDict = stringDict _file = file _accountDetails = accountDetails + _numDetails = numDetails super.init() } @@ -58,4 +61,5 @@ public final class MockRequest: MEGARequest { public override var megaStringDictionary: [String: String] { stringDict } public override var file: String? { _file } public override var megaAccountDetails: MEGAAccountDetails? { _accountDetails } + public override var numDetails: Int { _numDetails } } diff --git a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockSdk.swift b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockSdk.swift index e8f58ad099..d82260aa30 100644 --- a/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockSdk.swift +++ b/Modules/Repository/MEGASDKRepo/Sources/MEGASDKRepoMock/SDK/MockSdk.swift @@ -38,6 +38,7 @@ public final class MockSdk: MEGASdk { private let file: String? private let copiedNodeHandles: [MEGAHandle: MEGAHandle] private let abTestValues: [String: Int] + private let requestResult: Result public private(set) var sendEvent_Calls = [( eventType: Int, @@ -61,6 +62,7 @@ public final class MockSdk: MEGASdk { public var disablepkp: Bool? public var shareAccessLevel: MEGAShareType = .accessUnknown public var stopPublicSetPreviewCalled = 0 + public var authorizeNodeCalled = 0 public init(nodes: [MEGANode] = [], rubbishNodes: [MEGANode] = [], @@ -97,7 +99,8 @@ public final class MockSdk: MEGASdk { devices: [String: String] = [:], file: String? = nil, copiedNodeHandles: [MEGAHandle: MEGAHandle] = [:], - abTestValues: [String: Int] = [:] + abTestValues: [String: Int] = [:], + requestResult: Result = .failure(MockError.failingError) ) { self.nodes = nodes self.rubbishNodes = rubbishNodes @@ -135,6 +138,7 @@ public final class MockSdk: MEGASdk { self.file = file self.copiedNodeHandles = copiedNodeHandles self.abTestValues = abTestValues + self.requestResult = requestResult super.init() } @@ -488,7 +492,35 @@ public final class MockSdk: MEGASdk { } public override func authorizeNode(_ node: MEGANode) -> MEGANode? { - node + authorizeNodeCalled += 1 + return node + } + + public override func startDownloadNode(_ node: MEGANode, localPath: String, fileName: String?, appData: String?, startFirst: Bool, cancelToken: MEGACancelToken?, collisionCheck: CollisionCheck, collisionResolution: CollisionResolution, delegate: any MEGATransferDelegate) { + delegate.onTransferFinish?( + self, + transfer: MockTransfer(type: .download, nodeHandle: node.handle, parentHandle: node.parentHandle), + error: MockError(errorType: .apiOk)) + } + + // MARK: - ADS + + public override func fetchAds(_ adFlags: AdsFlag, adUnits: MEGAStringList, publicHandle: MEGAHandle, delegate: any MEGARequestDelegate) { + switch requestResult { + case .success(let request): + delegate.onRequestFinish?(self, request: request, error: MEGAError()) + case .failure(let error): + delegate.onRequestFinish?(self, request: MockRequest(handle: 1), error: error) + } + } + + public override func queryAds(_ adFlags: AdsFlag, publicHandle: MEGAHandle, delegate: any MEGARequestDelegate) { + switch requestResult { + case .success(let request): + delegate.onRequestFinish?(self, request: request, error: MEGAError()) + case .failure(let error): + delegate.onRequestFinish?(self, request: MockRequest(handle: 1), error: error) + } } } diff --git a/Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/AdsRepositoryTests.swift b/Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/AdsRepositoryTests.swift new file mode 100644 index 0000000000..e69e6e1a58 --- /dev/null +++ b/Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/AdsRepositoryTests.swift @@ -0,0 +1,59 @@ +import MEGASDKRepo +import MEGASDKRepoMock +import XCTest + +final class AdsRepositoryTests: XCTestCase { + + func testFetchAds_successRequest_shouldReturnCorrectAds() async throws { + let expectedAds = ["FILES": "https://testAd/link"] + let mockRequest = MockRequest(handle: 1, stringDict: expectedAds) + let sut = AdsRepository( + sdk: MockSdk(requestResult: .success(mockRequest)) + ) + + let adsResult = try await sut.fetchAds(adsFlag: .defaultAds, + adUnits: [.files], + publicHandle: .invalidHandle) + + XCTAssertEqual(adsResult, expectedAds) + } + + func testFetchAds_failedRequest_shouldReturnError() async throws { + let expectedError = MockError.failingError + let sut = AdsRepository( + sdk: MockSdk(requestResult: .failure(expectedError)) + ) + + await XCTAsyncAssertThrowsError(try await sut.fetchAds(adsFlag: .defaultAds, + adUnits: [.files], + publicHandle: .invalid) + ) { error in + XCTAssertEqual(error as? MockError, expectedError) + } + } + + func testQueryAds_successRequest_shouldReturnCorrectValue() async throws { + let expectedValue = Int.random(in: 0...1) + let mockRequest = MockRequest(handle: 1, numDetails: expectedValue) + let sut = AdsRepository( + sdk: MockSdk(requestResult: .success(mockRequest)) + ) + + let queryAdsValue = try await sut.queryAds(adsFlag: .defaultAds, publicHandle: .invalid) + + XCTAssertEqual(queryAdsValue, expectedValue) + } + + func testQueryAds_failedRequest_shouldReturnError() async throws { + let expectedError = MockError.failingError + let sut = AdsRepository( + sdk: MockSdk(requestResult: .failure(expectedError)) + ) + + await XCTAsyncAssertThrowsError(try await sut.queryAds(adsFlag: .defaultAds, + publicHandle: .invalid) + ) { error in + XCTAssertEqual(error as? MockError, expectedError) + } + } +} diff --git a/MEGADataTests/Repos/NodeAttributeRepositoryTests.swift b/Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/NodeAttributeRepositoryTests.swift similarity index 98% rename from MEGADataTests/Repos/NodeAttributeRepositoryTests.swift rename to Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/NodeAttributeRepositoryTests.swift index e5b0d21155..0c68bbd97a 100644 --- a/MEGADataTests/Repos/NodeAttributeRepositoryTests.swift +++ b/Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/NodeAttributeRepositoryTests.swift @@ -1,6 +1,5 @@ - -@testable import MEGA import MEGADomain +import MEGASDKRepo import MEGASDKRepoMock import XCTest diff --git a/MEGADataTests/Repos/NodeUpdateRepositoryTests.swift b/Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/NodeUpdateRepositoryTests.swift similarity index 98% rename from MEGADataTests/Repos/NodeUpdateRepositoryTests.swift rename to Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/NodeUpdateRepositoryTests.swift index de60cfedf2..2f7ad480f7 100644 --- a/MEGADataTests/Repos/NodeUpdateRepositoryTests.swift +++ b/Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/NodeUpdateRepositoryTests.swift @@ -1,5 +1,5 @@ -@testable import MEGA import MEGADomain +import MEGASDKRepo import MEGASDKRepoMock import XCTest diff --git a/MEGADataTests/Repos/RubbishBinRepositoryTests.swift b/Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/RubbishBinRepositoryTests.swift similarity index 93% rename from MEGADataTests/Repos/RubbishBinRepositoryTests.swift rename to Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/RubbishBinRepositoryTests.swift index 050f2239e8..0563f08932 100644 --- a/MEGADataTests/Repos/RubbishBinRepositoryTests.swift +++ b/Modules/Repository/MEGASDKRepo/Tests/MEGASDKRepoTests/RubbishBinRepositoryTests.swift @@ -1,5 +1,5 @@ -@testable import MEGA import MEGADomain +import MEGASDKRepo import MEGASDKRepoMock import XCTest @@ -25,8 +25,7 @@ final class RubbishBinRepositoryTests: XCTestCase { sdk = MockSdk(nodes: testNodesArray, syncDebrisNodes: syncDebrisNodes, rubbishBinNode: rubbishBinNode) - repo = RubbishBinRepository(sdk: sdk, - nodeValidationRepository: NodeValidationRepository(sdk: sdk)) + repo = RubbishBinRepository(sdk: sdk) } func test_isSyncDebrisNode() async throws { diff --git a/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/ActionSheet/ActionSheetHeaderView.swift b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/ActionSheet/ActionSheetHeaderView.swift new file mode 100644 index 0000000000..f275809fb7 --- /dev/null +++ b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/ActionSheet/ActionSheetHeaderView.swift @@ -0,0 +1,48 @@ +import SwiftUI + +public struct ActionSheetHeaderView: View { + @Environment(\.colorScheme) private var colorScheme + let iconName: String? + let title: String + let detailImageName: String? + let subtitle: String + let subtitleColorName: String + + public init(iconName: String? = nil, title: String, detailImageName: String? = nil, subtitle: String, subtitleColorName: String) { + self.iconName = iconName + self.title = title + self.detailImageName = detailImageName + self.subtitle = subtitle + self.subtitleColorName = subtitleColorName + } + + public var body: some View { + HStack { + if let iconName { + Image(iconName) + .scaledToFit() + .frame(width: 40, height: 40) + .padding(EdgeInsets(top: 10, leading: 12, bottom: 10, trailing: 8)) + } + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.subheadline) + .fontWeight(.medium) + .lineLimit(1) + .foregroundColor(colorScheme == .dark ? .white : .black) + HStack { + if let detailImageName { + Image(detailImageName) + .renderingMode(.template) + .foregroundColor(Color(subtitleColorName)) + .frame(width: 12, height: 12) + } + Text(subtitle) + .font(.caption) + .foregroundColor(Color(subtitleColorName)) + } + } + Spacer() + } + } +} diff --git a/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/ActionSheet/ActionSheetView.swift b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/ActionSheet/ActionSheetView.swift new file mode 100644 index 0000000000..4eb2f09fe5 --- /dev/null +++ b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/ActionSheet/ActionSheetView.swift @@ -0,0 +1,90 @@ +import SwiftUI + +public struct ActionSheetContentView: View { + @Environment(\.colorScheme) private var colorScheme + var actionButtons: [ActionSheetButton] + var headerView: HeaderView + + public init(headerView: HeaderView, actionButtons: [ActionSheetButton]) { + self.headerView = headerView + self.actionButtons = actionButtons + } + + public var body: some View { + ScrollView { + VStack(spacing: 0) { + VStack(spacing: 0) { + Capsule() + .fill(Color.secondary) + .opacity(0.5) + .frame(width: 36, height: 5) + .padding(5) + + headerView + .padding(.bottom, 2) + + Divider() + } + .background(colorScheme == .dark ? Color("2c2c2e") : Color("F7F7F7")) + + ForEach(actionButtons, id: \.self) { button in + button + } + } + .padding([.bottom], 30) + } + } +} + +public struct ActionSheetButton: View, Hashable { + var icon: String + var title: String + var subtitle: String? + var action: () -> Void + + public init(icon: String, title: String, subtitle: String? = nil, action: @escaping () -> Void) { + self.icon = icon + self.title = title + self.subtitle = subtitle + self.action = action + } + + public static func == (lhs: ActionSheetButton, rhs: ActionSheetButton) -> Bool { + lhs.icon == rhs.icon && lhs.title == rhs.title && lhs.subtitle == rhs.subtitle + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(icon) + hasher.combine(title) + hasher.combine(subtitle) + } + + public var body: some View { + VStack(spacing: 0) { + Button(action: action) { + HStack { + Image(icon) + .frame(width: 28, height: 28) + .padding(16) + + Text(title) + .font(.body) + + Spacer() + if let subtitle = subtitle { + Text(subtitle) + .font(.body) + + Image("standardDisclosureIndicator") + .padding([.trailing], 16) + .padding([.leading], 5) + } + } + .frame(maxWidth: .infinity) + } + .buttonStyle(PlainButtonStyle()) + Divider() + .padding([.leading], 60) + } + } +} diff --git a/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/TaskModifier.swift b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/TaskModifier.swift index 11d65303fb..5c779d2a0e 100644 --- a/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/TaskModifier.swift +++ b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/TaskModifier.swift @@ -33,15 +33,70 @@ public struct TaskModifier: ViewModifier { } } +public struct ThrowingTaskModifier: ViewModifier { + private var priority: TaskPriority + private var action: @Sendable () async throws -> Void + + public init( + priority: TaskPriority, + action: @escaping @Sendable () async throws -> Void + ) { + self.priority = priority + self.action = action + } + + @State private var task: Task? + + private func cancelTask() { + task?.cancel() + task = nil + } + + public func body(content: Content) -> some View { + if #available(iOS 15.0, *) { + content + .task(priority: priority) { + do { + try await action() + } catch { + debugPrint("Error occurred: \(error)") + cancelTask() + } + } + } else { + content + .onAppear { + task = Task(priority: priority) { + do { + try await action() + } catch { + debugPrint("Error occurred: \(error)") + cancelTask() + } + } + } + .onDisappear { + cancelTask() + } + } + } +} + public extension View { - /// Asynchronous task to perform on the view + + /// Asynchronous Task to Perform on the View (iOS 14 Compatibility) /// - /// iOS 15 and later will perform the task before view appears. See `task(priority:_:)` for more info. + /// This function offers a way to execute asynchronous tasks on a SwiftUI view, + /// especially when targeting iOS 14 and lower versions that lack the built-in `task(priority:_:)` modifier. /// - /// iOS 15 and lower it will create the task `onAppear` and cancel it `onDisappear`. + /// - Parameter priority: The priority at which the task should be executed. Default is `TaskPriority.userInitiated`. + /// - Parameter action: An asynchronous closure that will be performed as a task on the view. /// - /// - Parameter priority The task priority to use when creating the asynchronous task. The default priority is userInitiated. - /// - Parameter action A closure that SwiftUI calls as an asynchronous task + /// - Returns: A modified view that will execute the provided action asynchronously. + /// + /// - Note: On iOS 15 and later, this modifier is obsolete and has no effect due to the availability + /// of the native `task(priority:_:)` modifier. On iOS 14 and lower, it creates a custom `Task` + /// using `onAppear` and `onDisappear` modifiers to handle the asynchronous action. @available(iOS, obsoleted: 15.0, message: "task(priority:_:) is available on iOS 15.") func taskForiOS14( priority: TaskPriority = .userInitiated, @@ -49,4 +104,26 @@ public extension View { ) -> some View { modifier(TaskModifier(priority: priority, action: action)) } + + /// Asynchronous Throwing Task to Perform on the View (iOS 14 Compatibility) + /// + /// This function provides a way to execute asynchronous throwing tasks on a SwiftUI view, + /// especially when targeting iOS 14 and lower versions that lack the built-in `task(priority:_:)` modifier. + /// + /// - Parameter priority: The priority at which the task should be executed. Default is `TaskPriority.userInitiated`. + /// - Parameter action: A throwing asynchronous closure that will be performed as a task on the view. + /// + /// - Returns: A modified view that will execute the provided throwing action asynchronously. + /// + /// - Note: On iOS 15 and later, this modifier uses the native `task(priority:_:)` modifier + /// to execute the provided action. On iOS 14 and lower, it creates a custom `Task` + /// using `onAppear` and `onDisappear` modifiers to handle the asynchronous action. + /// If the action throws an error, the task will be cancelled. + @available(iOS, obsoleted: 15.0, message: "task(priority:_:) is available on iOS 15.") + func throwingTaskForiOS14( + priority: TaskPriority = .userInitiated, + @_inheritActorContext _ action: @escaping @Sendable () async throws -> Void + ) -> some View { + self.modifier(ThrowingTaskModifier(priority: priority, action: action)) + } } diff --git a/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/Text/FocusableTextFieldView.swift b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/Text/FocusableTextFieldView.swift new file mode 100644 index 0000000000..76ee0bfe0c --- /dev/null +++ b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/Text/FocusableTextFieldView.swift @@ -0,0 +1,66 @@ +import SwiftUI + +public struct FocusableTextFieldView: View { + let placeholder: String + @Binding var text: String + var appearFocused: Bool = false + let clearButtonMode: UITextField.ViewMode + + public init(placeholder: String, + text: Binding, + appearFocused: Bool, + clearButtonMode: UITextField.ViewMode = .never) { + self.placeholder = placeholder + _text = text + self.appearFocused = appearFocused + self.clearButtonMode = clearButtonMode + } + + public var body: some View { + if #available(iOS 15.0, *) { + FocusableTextField(placeholder: placeholder, + text: $text, + appearFocused: appearFocused, + clearButtonMode: clearButtonMode) + } else { + TextFieldView(placeholder: placeholder, + text: $text, + clearButtonMode: clearButtonMode) + } + } + + @available(iOS 15.0, *) + struct FocusableTextField: View { + let placeholder: String + @Binding var text: String + @FocusState var focused: Bool + let appearFocused: Bool + let clearButtonMode: UITextField.ViewMode + + var body: some View { + TextField(placeholder, text: $text) + .focused($focused) + .onAppear { + if clearButtonMode != .never { + UITextField.appearance().clearButtonMode = clearButtonMode + } + focused = appearFocused + } + } + } + + struct TextFieldView: View { + let placeholder: String + @Binding var text: String + let clearButtonMode: UITextField.ViewMode + + var body: some View { + TextField(placeholder, text: $text) + .onAppear { + if clearButtonMode != .never { + UITextField.appearance().clearButtonMode = clearButtonMode + } + } + } + } +} diff --git a/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/Text/SubheadlineTextView.swift b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/Text/SubheadlineTextView.swift index af01cd46e6..9b66414b65 100644 --- a/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/Text/SubheadlineTextView.swift +++ b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Custom Views/Text/SubheadlineTextView.swift @@ -1,4 +1,3 @@ - import SwiftUI public struct SubheadlineTextView: View { diff --git a/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Extensions/View+Additions.swift b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Extensions/View+Additions.swift index 43554adfc3..2c9d69d215 100644 --- a/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Extensions/View+Additions.swift +++ b/Modules/UI/MEGASwiftUI/Sources/MEGASwiftUI/Extensions/View+Additions.swift @@ -1,7 +1,7 @@ import SwiftUI import UIKit -extension View { +public extension View { func hideKeyboard() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } diff --git a/SearchDemo/ContentView.swift b/SearchDemo/ContentView.swift new file mode 100644 index 0000000000..4592c9ddbb --- /dev/null +++ b/SearchDemo/ContentView.swift @@ -0,0 +1,32 @@ +import SwiftUI +@testable import Search + +struct ContentView: View { + var body: some View { + NavigationView { + Wrapper() + } + } + struct Wrapper: View { + @State var text: String = "" + @StateObject var viewModel = SearchResultsViewModel( + resultsProvider: NonProductionTestResultsProvider(), + bridge: .init(selection: { _ in }, context: {_ in }) + ) + var body: some View { + SearchResultsView( + viewModel: viewModel + ) + .onChange(of: text, perform: { newValue in + viewModel.bridge.queryChanged(newValue) + }) + .searchable(text: $text) + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/SearchDemo/SearchDemoApp.swift b/SearchDemo/SearchDemoApp.swift new file mode 100644 index 0000000000..45fc09f97f --- /dev/null +++ b/SearchDemo/SearchDemoApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct SearchDemoApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/SwiftGen/swiftgen.yml b/SwiftGen/swiftgen.yml index 0dd7c8f9e8..1443e9579c 100644 --- a/SwiftGen/swiftgen.yml +++ b/SwiftGen/swiftgen.yml @@ -1,17 +1,6 @@ input_dir: ../iMEGA/ output_dir: ${DERIVED_SOURCES_DIR} -## Strings -strings: - inputs: - - Languages/Base.lproj - outputs: - - templateName: structured-swift5 - params: - publicAccess: true - enumName: Strings - output: Strings+Generated.swift - xcassets: - inputs: Colors.xcassets outputs: diff --git a/fastlane/.env.default b/fastlane/.env.default index f2d63b9ca9..1cd5803fee 100644 --- a/fastlane/.env.default +++ b/fastlane/.env.default @@ -6,6 +6,7 @@ MEGA_LIBRARIES_ZIP_DOWNLOAD_PATH = "./../download_3rdparty/" MEGA_LIBRARIES_UNZIP_PATH = "./Modules/DataSource/MEGASDK/Sources/MEGASDK/bindings/ios/3rdparty" SDK_THIRD_PARTY_PATH = "./Modules/DataSource/MEGASDK/Sources/MEGASDK/bindings/ios/3rdparty" CHAT_THIRD_PARTY_PATH = "./Modules/DataSource/MEGAChatSDK/Sources/MEGAChatSDK/bindings/Objective-C/3rdparty" +SWIFT_PACKAGES_PATH = "SwiftPackages" # Errors json path XCRESULT_FOLDER = "./../derivedData/Logs/Test/" diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 97eb07c971..6872f1fe43 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -269,22 +269,12 @@ platform :ios do desc "Run unit tests for main app" lane :run_tests_app do |options| - skip_package_dependencies_resolution = options[:skip_package_dependencies_resolution] || false - cloned_source_packages_path = File.join(File.expand_path('..', File.expand_path('..', Dir.pwd)), "SwiftPackages") - - skip_package_dependencies_resolution = should_skip_package_dependencies_resolution( - cloned_source_packages_path: cloned_source_packages_path - ) unless skip_package_dependencies_resolution and options[:skip_package_dependencies_resolution].nil? - - UI.message "Skipping the package dependencies resolution: #{skip_package_dependencies_resolution}" - scan( workspace: WORKSPACE, scheme: SCHEME, - devices: ['iPhone 14 Pro Max'], - cloned_source_packages_path: cloned_source_packages_path, + devices: ['iPhone 15 Pro Max'], + cloned_source_packages_path: ENV['SWIFT_PACKAGES_PATH'], disable_package_automatic_updates: true, - skip_package_dependencies_resolution: skip_package_dependencies_resolution, output_directory: ".", output_types: "junit", derived_data_path: "derivedData", @@ -296,39 +286,6 @@ platform :ios do ) end - desc "Checks if the cached swift packages matches the version mentioned in the `package.resolved` file." - private_lane :should_skip_package_dependencies_resolution do |options| - resolved_package_file = "./iMEGA.xcworkspace/xcshareddata/swiftpm/Package.resolved" - resolved_package_json = read_json(json_path: resolved_package_file) - - workspace_state_json_file = File.join(options[:cloned_source_packages_path], "workspace-state.json") - result = true - - if File.exist?(workspace_state_json_file) - workspace_state_json = read_json(json_path: workspace_state_json_file) - resolved_package_json[:pins].each do |package| - name = package[:identity] - version = package[:state][:version] - found = false - - workspace_state_json[:object][:dependencies].each do |downloaded_package| - package_name = downloaded_package[:packageRef][:identity] - downloaded_version = downloaded_package[:state][:checkoutState][:version] - found = true if name == package_name and version == downloaded_version - end - - unless found - result = false - break - end - end - else - result = false - end - - result - end - desc "Download metadata" lane :download_metadata do api_key_value = ENV['APP_STORE_CONNECT_API_KEY_VALUE'] diff --git a/iMEGA.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iMEGA.xcworkspace/xcshareddata/swiftpm/Package.resolved index fee4104126..4390fd08aa 100644 --- a/iMEGA.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/iMEGA.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -195,7 +195,7 @@ "location" : "https://code.developers.mega.co.nz/mobile/kmm/mobile-analytics-ios", "state" : { "branch" : "main", - "revision" : "eb4cc11dde4f188c7afc462f0e0f473f7d8bf7aa" + "revision" : "310e2ea70733a75eeab2dcdf15f8d00269495872" } }, { diff --git a/iMEGA/API/Chat/MEGAArchiveChatRequestDelegate.h b/iMEGA/API/Chat/MEGAArchiveChatRequestDelegate.h index bfa77498f8..128542698c 100644 --- a/iMEGA/API/Chat/MEGAArchiveChatRequestDelegate.h +++ b/iMEGA/API/Chat/MEGAArchiveChatRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGAChatBaseRequestDelegate.h" @interface MEGAArchiveChatRequestDelegate : MEGAChatBaseRequestDelegate diff --git a/iMEGA/API/Chat/MEGAArchiveChatRequestDelegate.m b/iMEGA/API/Chat/MEGAArchiveChatRequestDelegate.m index 1bf34005e1..36c4bbe761 100644 --- a/iMEGA/API/Chat/MEGAArchiveChatRequestDelegate.m +++ b/iMEGA/API/Chat/MEGAArchiveChatRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAArchiveChatRequestDelegate.h" @interface MEGAArchiveChatRequestDelegate () diff --git a/iMEGA/API/Chat/MEGAChatAnswerCallRequestDelegate.h b/iMEGA/API/Chat/MEGAChatAnswerCallRequestDelegate.h index 8bd1f4818e..2f1b20017f 100644 --- a/iMEGA/API/Chat/MEGAChatAnswerCallRequestDelegate.h +++ b/iMEGA/API/Chat/MEGAChatAnswerCallRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGAChatBaseRequestDelegate.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/API/Chat/MEGAChatAnswerCallRequestDelegate.m b/iMEGA/API/Chat/MEGAChatAnswerCallRequestDelegate.m index a6d555eaed..9ebcd030f6 100644 --- a/iMEGA/API/Chat/MEGAChatAnswerCallRequestDelegate.m +++ b/iMEGA/API/Chat/MEGAChatAnswerCallRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAChatAnswerCallRequestDelegate.h" @interface MEGAChatAnswerCallRequestDelegate () diff --git a/iMEGA/API/Chat/MEGAChatAttachNodeRequestDelegate.h b/iMEGA/API/Chat/MEGAChatAttachNodeRequestDelegate.h index 6e35c2d0db..70a04f90f5 100644 --- a/iMEGA/API/Chat/MEGAChatAttachNodeRequestDelegate.h +++ b/iMEGA/API/Chat/MEGAChatAttachNodeRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGAChatBaseRequestDelegate.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/API/Chat/MEGAChatAttachNodeRequestDelegate.m b/iMEGA/API/Chat/MEGAChatAttachNodeRequestDelegate.m index 768565c51a..8584066d6b 100644 --- a/iMEGA/API/Chat/MEGAChatAttachNodeRequestDelegate.m +++ b/iMEGA/API/Chat/MEGAChatAttachNodeRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAChatAttachNodeRequestDelegate.h" @interface MEGAChatAttachNodeRequestDelegate () diff --git a/iMEGA/API/Chat/MEGAChatAttachVoiceClipRequestDelegate.h b/iMEGA/API/Chat/MEGAChatAttachVoiceClipRequestDelegate.h index 866dcb225b..06e3602446 100644 --- a/iMEGA/API/Chat/MEGAChatAttachVoiceClipRequestDelegate.h +++ b/iMEGA/API/Chat/MEGAChatAttachVoiceClipRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGAChatBaseRequestDelegate.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/API/Chat/MEGAChatAttachVoiceClipRequestDelegate.m b/iMEGA/API/Chat/MEGAChatAttachVoiceClipRequestDelegate.m index 548b548b50..e47099f567 100644 --- a/iMEGA/API/Chat/MEGAChatAttachVoiceClipRequestDelegate.m +++ b/iMEGA/API/Chat/MEGAChatAttachVoiceClipRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAChatAttachVoiceClipRequestDelegate.h" @interface MEGAChatAttachVoiceClipRequestDelegate () diff --git a/iMEGA/API/Chat/MEGAChatBaseRequestDelegate.h b/iMEGA/API/Chat/MEGAChatBaseRequestDelegate.h index a6756d97c8..5c5244cc4a 100644 --- a/iMEGA/API/Chat/MEGAChatBaseRequestDelegate.h +++ b/iMEGA/API/Chat/MEGAChatBaseRequestDelegate.h @@ -1,4 +1,3 @@ - #import #import "MEGAChatSdk.h" diff --git a/iMEGA/API/Chat/MEGAChatBaseRequestDelegate.m b/iMEGA/API/Chat/MEGAChatBaseRequestDelegate.m index e745f0fb71..23f93398d0 100644 --- a/iMEGA/API/Chat/MEGAChatBaseRequestDelegate.m +++ b/iMEGA/API/Chat/MEGAChatBaseRequestDelegate.m @@ -1,8 +1,9 @@ - #import "MEGAChatBaseRequestDelegate.h" #import "SVProgressHUD.h" +@import MEGAL10nObjc; + @implementation MEGAChatBaseRequestDelegate - (void)onChatRequestFinish:(MEGAChatSdk *)api request:(MEGAChatRequest *)request error:(MEGAChatError *)error { @@ -19,7 +20,7 @@ - (void)onChatRequestFinish:(MEGAChatSdk *)api request:(MEGAChatRequest *)reques return; } if ((request.type == MEGAChatRequestTypeAnswerChatCall || request.type == MEGAChatRequestTypeStartChatCall) && error.type == MEGAChatErrorTooMany) { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"Error. No more participants are allowed in this group call.", @"Message show when a call cannot be established because there are too many participants in the group call")]; + [SVProgressHUD showErrorWithStatus:LocalizedString(@"Error. No more participants are allowed in this group call.", @"Message show when a call cannot be established because there are too many participants in the group call")]; return; } if ((request.type == MEGAChatRequestTypeAutojoinPublicChat && error.type == MEGAChatErrorTypeArgs) @@ -28,7 +29,7 @@ - (void)onChatRequestFinish:(MEGAChatSdk *)api request:(MEGAChatRequest *)reques } [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; #endif } } diff --git a/iMEGA/API/Chat/MEGAChatChangeGroupNameRequestDelegate.h b/iMEGA/API/Chat/MEGAChatChangeGroupNameRequestDelegate.h index 000df132c8..dfe0a937e2 100644 --- a/iMEGA/API/Chat/MEGAChatChangeGroupNameRequestDelegate.h +++ b/iMEGA/API/Chat/MEGAChatChangeGroupNameRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGAChatBaseRequestDelegate.h" @interface MEGAChatChangeGroupNameRequestDelegate : MEGAChatBaseRequestDelegate diff --git a/iMEGA/API/Chat/MEGAChatChangeGroupNameRequestDelegate.m b/iMEGA/API/Chat/MEGAChatChangeGroupNameRequestDelegate.m index 56ab44f14d..4bb1226769 100644 --- a/iMEGA/API/Chat/MEGAChatChangeGroupNameRequestDelegate.m +++ b/iMEGA/API/Chat/MEGAChatChangeGroupNameRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAChatChangeGroupNameRequestDelegate.h" @interface MEGAChatChangeGroupNameRequestDelegate () diff --git a/iMEGA/API/Chat/MEGAChatEnableDisableAudioRequestDelegate.h b/iMEGA/API/Chat/MEGAChatEnableDisableAudioRequestDelegate.h index 61c627e81d..460514dff7 100644 --- a/iMEGA/API/Chat/MEGAChatEnableDisableAudioRequestDelegate.h +++ b/iMEGA/API/Chat/MEGAChatEnableDisableAudioRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGAChatBaseRequestDelegate.h" @interface MEGAChatEnableDisableAudioRequestDelegate : MEGAChatBaseRequestDelegate diff --git a/iMEGA/API/Chat/MEGAChatEnableDisableAudioRequestDelegate.m b/iMEGA/API/Chat/MEGAChatEnableDisableAudioRequestDelegate.m index a390458c7e..19ad61bee0 100644 --- a/iMEGA/API/Chat/MEGAChatEnableDisableAudioRequestDelegate.m +++ b/iMEGA/API/Chat/MEGAChatEnableDisableAudioRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAChatEnableDisableAudioRequestDelegate.h" @interface MEGAChatEnableDisableAudioRequestDelegate () diff --git a/iMEGA/API/Chat/MEGAChatGenericRequestDelegate.h b/iMEGA/API/Chat/MEGAChatGenericRequestDelegate.h index 939df5b131..5d32b45672 100644 --- a/iMEGA/API/Chat/MEGAChatGenericRequestDelegate.h +++ b/iMEGA/API/Chat/MEGAChatGenericRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGAChatBaseRequestDelegate.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/API/Chat/MEGAChatGenericRequestDelegate.m b/iMEGA/API/Chat/MEGAChatGenericRequestDelegate.m index 0f6ed95c39..781b462ef2 100644 --- a/iMEGA/API/Chat/MEGAChatGenericRequestDelegate.m +++ b/iMEGA/API/Chat/MEGAChatGenericRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAChatGenericRequestDelegate.h" @interface MEGAChatGenericRequestDelegate () diff --git a/iMEGA/API/Chat/MEGAChatNotificationDelegate.h b/iMEGA/API/Chat/MEGAChatNotificationDelegate.h index 6d59b71a26..069cdb7db0 100644 --- a/iMEGA/API/Chat/MEGAChatNotificationDelegate.h +++ b/iMEGA/API/Chat/MEGAChatNotificationDelegate.h @@ -1,4 +1,3 @@ - #import #import "MEGAChatSdk.h" diff --git a/iMEGA/API/Chat/MEGAChatNotificationDelegate.m b/iMEGA/API/Chat/MEGAChatNotificationDelegate.m index 561fbaafcb..a75b570466 100644 --- a/iMEGA/API/Chat/MEGAChatNotificationDelegate.m +++ b/iMEGA/API/Chat/MEGAChatNotificationDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAChatNotificationDelegate.h" #import "MEGALocalNotificationManager.h" diff --git a/iMEGA/API/Chat/MEGAChatStartCallRequestDelegate.h b/iMEGA/API/Chat/MEGAChatStartCallRequestDelegate.h index d4828b8a17..09fd2ff394 100644 --- a/iMEGA/API/Chat/MEGAChatStartCallRequestDelegate.h +++ b/iMEGA/API/Chat/MEGAChatStartCallRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGAChatBaseRequestDelegate.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/API/Chat/MEGAChatStartCallRequestDelegate.m b/iMEGA/API/Chat/MEGAChatStartCallRequestDelegate.m index d01a6ebf59..0d9d9d1c4e 100644 --- a/iMEGA/API/Chat/MEGAChatStartCallRequestDelegate.m +++ b/iMEGA/API/Chat/MEGAChatStartCallRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAChatStartCallRequestDelegate.h" @interface MEGAChatStartCallRequestDelegate () diff --git a/iMEGA/API/Requests/MEGAContactLinkCreateRequestDelegate.h b/iMEGA/API/Requests/MEGAContactLinkCreateRequestDelegate.h index 5cb594926d..54c270f42c 100644 --- a/iMEGA/API/Requests/MEGAContactLinkCreateRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAContactLinkCreateRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/API/Requests/MEGAContactLinkCreateRequestDelegate.m b/iMEGA/API/Requests/MEGAContactLinkCreateRequestDelegate.m index 200455ae74..c0bbd7c418 100644 --- a/iMEGA/API/Requests/MEGAContactLinkCreateRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAContactLinkCreateRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAContactLinkCreateRequestDelegate.h" @interface MEGAContactLinkCreateRequestDelegate () diff --git a/iMEGA/API/Requests/MEGAContactLinkQueryRequestDelegate.h b/iMEGA/API/Requests/MEGAContactLinkQueryRequestDelegate.h index 18916c8584..980cab1156 100644 --- a/iMEGA/API/Requests/MEGAContactLinkQueryRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAContactLinkQueryRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGAContactLinkQueryRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGAContactLinkQueryRequestDelegate.m b/iMEGA/API/Requests/MEGAContactLinkQueryRequestDelegate.m index 9405ea7727..b67f5ee35d 100644 --- a/iMEGA/API/Requests/MEGAContactLinkQueryRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAContactLinkQueryRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAContactLinkQueryRequestDelegate.h" @interface MEGAContactLinkQueryRequestDelegate () diff --git a/iMEGA/API/Requests/MEGACopyRequestDelegate.h b/iMEGA/API/Requests/MEGACopyRequestDelegate.h index d02e430aea..d27788dbfd 100755 --- a/iMEGA/API/Requests/MEGACopyRequestDelegate.h +++ b/iMEGA/API/Requests/MEGACopyRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGACopyRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGACopyRequestDelegate.m b/iMEGA/API/Requests/MEGACopyRequestDelegate.m index e3d9fead60..66d73b4446 100755 --- a/iMEGA/API/Requests/MEGACopyRequestDelegate.m +++ b/iMEGA/API/Requests/MEGACopyRequestDelegate.m @@ -1,8 +1,9 @@ - #import "MEGACopyRequestDelegate.h" #import "SVProgressHUD.h" +@import MEGAL10nObjc; + @interface MEGACopyRequestDelegate () @property (nonatomic, copy) void (^completion)(MEGARequest *request); @@ -27,7 +28,7 @@ - (instancetype)initWithCompletion:(void (^)(MEGARequest *request))completion { - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if (error.type) { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; return; } diff --git a/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.h b/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.h index 78e3e5a619..cae7e34136 100644 --- a/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.h +++ b/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGACreateAccountRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.m b/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.m index be6c41058b..3e89f461bc 100644 --- a/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.m +++ b/iMEGA/API/Requests/MEGACreateAccountRequestDelegate.m @@ -1,10 +1,11 @@ - #import "MEGACreateAccountRequestDelegate.h" #import "MEGALoginRequestDelegate.h" #import "SVProgressHUD.h" #import "UIApplication+MNZCategory.h" + +@import MEGAL10nObjc; @import SAMKeychain; @interface MEGACreateAccountRequestDelegate () @@ -41,13 +42,13 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG switch (error.type) { case MEGAErrorTypeApiEExist: { - NSString *message = NSLocalizedString(@"emailAlreadyRegistered", @"Error text shown when the users tries to create an account with an email already in use"); + NSString *message = LocalizedString(@"emailAlreadyRegistered", @"Error text shown when the users tries to create an account with an email already in use"); [SVProgressHUD showErrorWithStatus:message]; break; } default: { - NSString *message = [NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]; + NSString *message = [NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]; [SVProgressHUD showErrorWithStatus:message]; break; } diff --git a/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.h b/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.h index 4aa9904ef5..bb02658719 100755 --- a/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.h +++ b/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGACreateFolderRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.m b/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.m index 137abe132b..1bbade5a58 100755 --- a/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.m +++ b/iMEGA/API/Requests/MEGACreateFolderRequestDelegate.m @@ -1,10 +1,11 @@ - #import "MEGACreateFolderRequestDelegate.h" #import "SVProgressHUD.h" #import "UIApplication+MNZCategory.h" +@import MEGAL10nObjc; + @interface MEGACreateFolderRequestDelegate () @property (nonatomic, copy) void (^completion)(MEGARequest *request); @@ -29,12 +30,12 @@ - (instancetype)initWithCompletion:(void (^)(MEGARequest *request))completion { - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if (error.type) { if (error.type == MEGAErrorTypeApiEAccess) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"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:NSLocalizedString(@"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:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"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:LocalizedString(@"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:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } else if (error.type != MEGAErrorTypeApiEBusinessPastDue) { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; } return; } diff --git a/iMEGA/API/Requests/MEGAExportRequestDelegate.h b/iMEGA/API/Requests/MEGAExportRequestDelegate.h index a3fefb82f3..640742587a 100644 --- a/iMEGA/API/Requests/MEGAExportRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAExportRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGAExportRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGAExportRequestDelegate.m b/iMEGA/API/Requests/MEGAExportRequestDelegate.m index 39d4605b59..3470c48e4d 100644 --- a/iMEGA/API/Requests/MEGAExportRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAExportRequestDelegate.m @@ -1,8 +1,9 @@ - #import "MEGAExportRequestDelegate.h" #import "SVProgressHUD.h" +@import MEGAL10nObjc; + @interface MEGAExportRequestDelegate () @property (nonatomic, copy) void (^completion)(MEGARequest *request); @@ -26,7 +27,7 @@ - (instancetype)initWithCompletion:(void (^)(MEGARequest *request))completion mu - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { if (request.access) { - NSString *status = self.multipleLinks ? NSLocalizedString(@"generatingLinks", @"Message shown when some links to files and/or folders are being generated") : NSLocalizedString(@"generatingLink", @"Message shown when some links to files and/or folders are being generated"); + NSString *status = self.multipleLinks ? LocalizedString(@"generatingLinks", @"Message shown when some links to files and/or folders are being generated") : LocalizedString(@"generatingLink", @"Message shown when some links to files and/or folders are being generated"); [SVProgressHUD showWithStatus:status]; } else { [SVProgressHUD show]; @@ -38,7 +39,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (error.type == MEGAErrorTypeApiEBusinessPastDue) { [SVProgressHUD dismiss]; } else { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(error.name, nil)]; + [SVProgressHUD showErrorWithStatus:LocalizedString(error.name, @"")]; } return; } diff --git a/iMEGA/API/Requests/MEGAFetchNodesRequestDelegate.h b/iMEGA/API/Requests/MEGAFetchNodesRequestDelegate.h index 7f47ee893f..423478e04b 100644 --- a/iMEGA/API/Requests/MEGAFetchNodesRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAFetchNodesRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGAFetchNodesRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGAFetchNodesRequestDelegate.m b/iMEGA/API/Requests/MEGAFetchNodesRequestDelegate.m index 6c1b84fed7..08e5ca9586 100644 --- a/iMEGA/API/Requests/MEGAFetchNodesRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAFetchNodesRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAFetchNodesRequestDelegate.h" @interface MEGAFetchNodesRequestDelegate () diff --git a/iMEGA/API/Requests/MEGAGenericRequestDelegate.h b/iMEGA/API/Requests/MEGAGenericRequestDelegate.h index dc0fad2df4..4dc41d2188 100644 --- a/iMEGA/API/Requests/MEGAGenericRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAGenericRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/API/Requests/MEGAGenericRequestDelegate.m b/iMEGA/API/Requests/MEGAGenericRequestDelegate.m index 2ebcaf5962..e68376277c 100644 --- a/iMEGA/API/Requests/MEGAGenericRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAGenericRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAGenericRequestDelegate.h" @interface MEGAGenericRequestDelegate () diff --git a/iMEGA/API/Requests/MEGAGetAttrUserRequestDelegate.h b/iMEGA/API/Requests/MEGAGetAttrUserRequestDelegate.h index 27e936a7df..4509ddce1b 100644 --- a/iMEGA/API/Requests/MEGAGetAttrUserRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAGetAttrUserRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGAGetAttrUserRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGAGetAttrUserRequestDelegate.m b/iMEGA/API/Requests/MEGAGetAttrUserRequestDelegate.m index fa104b4df8..3c9470e788 100644 --- a/iMEGA/API/Requests/MEGAGetAttrUserRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAGetAttrUserRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAGetAttrUserRequestDelegate.h" @interface MEGAGetAttrUserRequestDelegate () diff --git a/iMEGA/API/Requests/MEGAGetPreviewRequestDelegate.h b/iMEGA/API/Requests/MEGAGetPreviewRequestDelegate.h index 5e1b4af812..f1dc91a6b7 100644 --- a/iMEGA/API/Requests/MEGAGetPreviewRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAGetPreviewRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGAGetPreviewRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGAGetPreviewRequestDelegate.m b/iMEGA/API/Requests/MEGAGetPreviewRequestDelegate.m index c7ab372b08..f9e32617ff 100644 --- a/iMEGA/API/Requests/MEGAGetPreviewRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAGetPreviewRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAGetPreviewRequestDelegate.h" @interface MEGAGetPreviewRequestDelegate () diff --git a/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.h b/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.h index 2d53a2436a..3f93073e9e 100644 --- a/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGAGetPublicNodeRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.m b/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.m index da23fd8b33..b85591931a 100644 --- a/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAGetPublicNodeRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAGetPublicNodeRequestDelegate.h" @interface MEGAGetPublicNodeRequestDelegate () diff --git a/iMEGA/API/Requests/MEGAGetThumbnailRequestDelegate.h b/iMEGA/API/Requests/MEGAGetThumbnailRequestDelegate.h index bd4755832a..9cf8d64879 100644 --- a/iMEGA/API/Requests/MEGAGetThumbnailRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAGetThumbnailRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/API/Requests/MEGAGetThumbnailRequestDelegate.m b/iMEGA/API/Requests/MEGAGetThumbnailRequestDelegate.m index b6946ce22e..e19c551235 100644 --- a/iMEGA/API/Requests/MEGAGetThumbnailRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAGetThumbnailRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAGetThumbnailRequestDelegate.h" @interface MEGAGetThumbnailRequestDelegate () diff --git a/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.h b/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.h index 8789e8e4e3..e83b4451a1 100755 --- a/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.m b/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.m index daae4746a6..90354ab538 100755 --- a/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAInviteContactRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAInviteContactRequestDelegate.h" #import @@ -6,6 +5,8 @@ #import "SVProgressHUD.h" #import "CustomModalAlertViewController.h" #import "UIApplication+MNZCategory.h" + +@import MEGAL10nObjc; @import MEGASDKRepo; @interface MEGAInviteContactRequestDelegate () @@ -53,7 +54,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG switch (error.type) { case MEGAErrorTypeApiEArgs: if ([request.email isEqualToString:MEGASdk.currentUserEmail]) { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"noNeedToAddYourOwnEmailAddress", @"Add contacts and share dialog error message when user try to add your own email address")]; + [SVProgressHUD showErrorWithStatus:LocalizedString(@"noNeedToAddYourOwnEmailAddress", @"Add contacts and share dialog error message when user try to add your own email address")]; } break; @@ -62,13 +63,13 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (user && user.visibility == MEGAUserVisibilityVisible) { [SVProgressHUD showErrorWithStatus:({ - [NSLocalizedString(@"alreadyAContact", @"Error message displayed when trying to invite a contact who is already added.") stringByReplacingOccurrencesOfString:@"%s" withString:request.email]; + [LocalizedString(@"alreadyAContact", @"Error message displayed when trying to invite a contact who is already added.") stringByReplacingOccurrencesOfString:@"%s" withString:request.email]; })]; } else { BOOL isInOutgoingContactRequest = NO; MEGAContactRequestList *outgoingContactRequestList = [api outgoingContactRequests]; - for (NSInteger i = 0; i < outgoingContactRequestList.size.integerValue; i++) { + for (NSInteger i = 0; i < outgoingContactRequestList.size; i++) { MEGAContactRequest *contactRequest = [outgoingContactRequestList contactRequestAtIndex:i]; if ([request.email isEqualToString:contactRequest.targetEmail]) { isInOutgoingContactRequest = YES; @@ -76,7 +77,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } } if (isInOutgoingContactRequest) { - NSString *statusText = NSLocalizedString(@"dialog.inviteContact.outgoingContactRequest", @"Detail message shown when a contact has been invited. The [X] placeholder will be replaced on runtime for the email of the invited user"); + NSString *statusText = LocalizedString(@"dialog.inviteContact.outgoingContactRequest", @"Detail message shown when a contact has been invited. The [X] placeholder will be replaced on runtime for the email of the invited user"); statusText = [statusText stringByReplacingOccurrencesOfString:@"[X]" withString:request.email]; [SVProgressHUD showErrorWithStatus:statusText]; } @@ -87,7 +88,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } default: - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; break; } @@ -100,18 +101,18 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG NSString *detailText; if (self.totalRequests > 1) { - detailText = NSLocalizedString(@"theUsersHaveBeenInvited", @"Success message shown when some contacts have been invited"); + detailText = LocalizedString(@"theUsersHaveBeenInvited", @"Success message shown when some contacts have been invited"); } else { - detailText = NSLocalizedString(@"dialog.inviteContact.outgoingContactRequest", @"Detail message shown when a contact has been invited. The [X] placeholder will be replaced on runtime for the email of the invited user"); + detailText = LocalizedString(@"dialog.inviteContact.outgoingContactRequest", @"Detail message shown when a contact has been invited. The [X] placeholder will be replaced on runtime for the email of the invited user"); detailText = [detailText stringByReplacingOccurrencesOfString:@"[X]" withString:request.email]; } CustomModalAlertViewController *customModalAlertVC = [[CustomModalAlertViewController alloc] init]; customModalAlertVC.image = [UIImage imageNamed:@"inviteSent"]; - customModalAlertVC.viewTitle = NSLocalizedString(@"inviteSent", @"Title shown when the user sends a contact invitation"); + customModalAlertVC.viewTitle = LocalizedString(@"inviteSent", @"Title shown when the user sends a contact invitation"); customModalAlertVC.detail = detailText; customModalAlertVC.boldInDetail = request.email; - customModalAlertVC.firstButtonTitle = NSLocalizedString(@"close", nil); + customModalAlertVC.firstButtonTitle = LocalizedString(@"close", @""); __weak typeof(CustomModalAlertViewController) *weakCustom = customModalAlertVC; customModalAlertVC.firstCompletion = ^{ [weakCustom dismissViewControllerAnimated:YES completion:^{ diff --git a/iMEGA/API/Requests/MEGALoginRequestDelegate.h b/iMEGA/API/Requests/MEGALoginRequestDelegate.h index 36ce7954f6..b7c105c534 100644 --- a/iMEGA/API/Requests/MEGALoginRequestDelegate.h +++ b/iMEGA/API/Requests/MEGALoginRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGALoginRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGALoginRequestDelegate.m b/iMEGA/API/Requests/MEGALoginRequestDelegate.m index b0ae4c96a4..3cd353d2f6 100644 --- a/iMEGA/API/Requests/MEGALoginRequestDelegate.m +++ b/iMEGA/API/Requests/MEGALoginRequestDelegate.m @@ -6,6 +6,7 @@ #import "NSString+MNZCategory.h" #import "UIApplication+MNZCategory.h" +@import MEGAL10nObjc; @import SAMKeychain; @interface MEGALoginRequestDelegate () @@ -64,7 +65,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG switch ([error type]) { case MEGAErrorTypeApiEArgs: case MEGAErrorTypeApiENoent: - message = NSLocalizedString(@"invalidMailOrPassword", @"Message shown when the user writes a wrong email or password on login"); + message = LocalizedString(@"invalidMailOrPassword", @"Message shown when the user writes a wrong email or password on login"); // The email or password have been changed in other client while the app requires the 2fa code if ((error.type == MEGAErrorTypeApiENoent) && request.text) { @@ -79,7 +80,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG self.errorCompletion(error); return; } else { - message = [NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]; + message = [NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]; break; } } @@ -89,11 +90,11 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG return; case MEGAErrorTypeApiETooMany: - message = [NSString stringWithFormat:NSLocalizedString(@"tooManyAttemptsLogin", @"Error message when to many attempts to login"), [self timeFormatted:3600]]; + message = [NSString stringWithFormat:LocalizedString(@"tooManyAttemptsLogin", @"Error message when to many attempts to login"), [self timeFormatted:3600]]; break; case MEGAErrorTypeApiEIncomplete: - message = NSLocalizedString(@"accountNotConfirmed", @"Text shown just after creating an account to remenber the user what to do to complete the account creation proccess"); + message = LocalizedString(@"accountNotConfirmed", @"Text shown just after creating an account to remenber the user what to do to complete the account creation proccess"); break; case MEGAErrorTypeApiESid: @@ -105,12 +106,12 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG return; default: - message = [NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]; + message = [NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]; break; } - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"error", nil) message:message preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"error", @"") message:message preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; diff --git a/iMEGA/API/Requests/MEGALoginToFolderLinkRequestDelegate.h b/iMEGA/API/Requests/MEGALoginToFolderLinkRequestDelegate.h index 381c00e143..48cc978a86 100644 --- a/iMEGA/API/Requests/MEGALoginToFolderLinkRequestDelegate.h +++ b/iMEGA/API/Requests/MEGALoginToFolderLinkRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGALoginToFolderLinkRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGALoginToFolderLinkRequestDelegate.m b/iMEGA/API/Requests/MEGALoginToFolderLinkRequestDelegate.m index 3c273a6356..f2670c3858 100644 --- a/iMEGA/API/Requests/MEGALoginToFolderLinkRequestDelegate.m +++ b/iMEGA/API/Requests/MEGALoginToFolderLinkRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGALoginToFolderLinkRequestDelegate.h" @interface MEGALoginToFolderLinkRequestDelegate () diff --git a/iMEGA/API/Requests/MEGAMoveRequestDelegate.h b/iMEGA/API/Requests/MEGAMoveRequestDelegate.h index 500cc6290b..51ac610e4b 100755 --- a/iMEGA/API/Requests/MEGAMoveRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAMoveRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/API/Requests/MEGAMoveRequestDelegate.m b/iMEGA/API/Requests/MEGAMoveRequestDelegate.m index 4a3e58d352..4114b0e718 100755 --- a/iMEGA/API/Requests/MEGAMoveRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAMoveRequestDelegate.m @@ -1,8 +1,9 @@ - #import "MEGAMoveRequestDelegate.h" #import "SVProgressHUD.h" +@import MEGAL10nObjc; + #ifdef MNZ_SHARE_EXTENSION #import "MEGAShare-Swift.h" #else @@ -68,7 +69,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if ((error.type == MEGAErrorTypeApiEBusinessPastDue) || (error.type == MEGAErrorTypeApiEOverQuota)) { [SVProgressHUD dismiss]; } else { - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; } return; } @@ -86,25 +87,25 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG NSString *message; if (self.numberOfFiles == 0) { if (self.numberOfFolders == 1) { - message = NSLocalizedString(@"moveFolderMessage", @"Success message shown when you have moved 1 folder"); + message = LocalizedString(@"moveFolderMessage", @"Success message shown when you have moved 1 folder"); } else { - message = [NSString stringWithFormat:NSLocalizedString(@"moveFoldersMessage", @"Success message shown when you have moved {1+} folders"), self.numberOfFolders]; + message = [NSString stringWithFormat:LocalizedString(@"moveFoldersMessage", @"Success message shown when you have moved {1+} folders"), self.numberOfFolders]; } } else if (self.numberOfFiles == 1) { if (self.numberOfFolders == 0) { - message = NSLocalizedString(@"moveFileMessage", @"Success message shown when you have moved 1 file"); + message = LocalizedString(@"moveFileMessage", @"Success message shown when you have moved 1 file"); } else if (self.numberOfFolders == 1) { - message = NSLocalizedString(@"moveFileFolderMessage", @"Success message shown when you have moved 1 file and 1 folder"); + message = LocalizedString(@"moveFileFolderMessage", @"Success message shown when you have moved 1 file and 1 folder"); } else { - message = [NSString stringWithFormat:NSLocalizedString(@"moveFileFoldersMessage", @"Success message shown when you have moved 1 file and {1+} folders"), self.numberOfFolders]; + message = [NSString stringWithFormat:LocalizedString(@"moveFileFoldersMessage", @"Success message shown when you have moved 1 file and {1+} folders"), self.numberOfFolders]; } } else { if (self.numberOfFolders == 0) { - message = [NSString stringWithFormat:NSLocalizedString(@"moveFilesMessage", @"Success message shown when you have moved {1+} files"), self.numberOfFiles]; + message = [NSString stringWithFormat:LocalizedString(@"moveFilesMessage", @"Success message shown when you have moved {1+} files"), self.numberOfFiles]; } else if (self.numberOfFolders == 1) { - message = [NSString stringWithFormat:NSLocalizedString(@"moveFilesFolderMessage", @"Success message shown when you have moved {1+} files and 1 folder"), self.numberOfFiles]; + message = [NSString stringWithFormat:LocalizedString(@"moveFilesFolderMessage", @"Success message shown when you have moved {1+} files and 1 folder"), self.numberOfFiles]; } else { - message = NSLocalizedString(@"moveFilesFoldersMessage", @"Success message shown when you have moved [A] = {1+} files and [B] = {1+} folders"); + message = LocalizedString(@"moveFilesFoldersMessage", @"Success message shown when you have moved [A] = {1+} files and [B] = {1+} folders"); NSString *filesString = [NSString stringWithFormat:@"%tu", self.numberOfFiles]; NSString *foldersString = [NSString stringWithFormat:@"%tu", self.numberOfFolders]; message = [message stringByReplacingOccurrencesOfString:@"[A]" withString:filesString]; diff --git a/iMEGA/API/Requests/MEGAMultiFactorAuthCheckRequestDelegate.h b/iMEGA/API/Requests/MEGAMultiFactorAuthCheckRequestDelegate.h index aa56f29a90..47058a85ae 100644 --- a/iMEGA/API/Requests/MEGAMultiFactorAuthCheckRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAMultiFactorAuthCheckRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGAMultiFactorAuthCheckRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGAMultiFactorAuthCheckRequestDelegate.m b/iMEGA/API/Requests/MEGAMultiFactorAuthCheckRequestDelegate.m index 4d39477950..75a85ee2a7 100644 --- a/iMEGA/API/Requests/MEGAMultiFactorAuthCheckRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAMultiFactorAuthCheckRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAMultiFactorAuthCheckRequestDelegate.h" @interface MEGAMultiFactorAuthCheckRequestDelegate () diff --git a/iMEGA/API/Requests/MEGAPasswordLinkRequestDelegate.h b/iMEGA/API/Requests/MEGAPasswordLinkRequestDelegate.h index e4d5ebb7ba..2e77bfd09c 100644 --- a/iMEGA/API/Requests/MEGAPasswordLinkRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAPasswordLinkRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGAPasswordLinkRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGAPasswordLinkRequestDelegate.m b/iMEGA/API/Requests/MEGAPasswordLinkRequestDelegate.m index 2cba011669..fb4a93d6b7 100644 --- a/iMEGA/API/Requests/MEGAPasswordLinkRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAPasswordLinkRequestDelegate.m @@ -1,7 +1,7 @@ - #import "MEGAPasswordLinkRequestDelegate.h" #import "SVProgressHUD.h" +@import MEGAL10nObjc; @interface MEGAPasswordLinkRequestDelegate () @@ -38,14 +38,14 @@ - (instancetype)initForDecryptionWithCompletion:(void (^)(MEGARequest *request)) - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { if (!self.forDecryption) { - NSString *status = self.multipleLinks ? NSLocalizedString(@"generatingLinks", nil) : NSLocalizedString(@"generatingLink", nil); + NSString *status = self.multipleLinks ? LocalizedString(@"generatingLinks", @"") : LocalizedString(@"generatingLink", @""); [SVProgressHUD showWithStatus:status]; } } - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if ([error type] && !self.forDecryption) { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(error.name, nil)]; + [SVProgressHUD showErrorWithStatus:LocalizedString(error.name, @"")]; return; } diff --git a/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.h b/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.h index c6e79a6fbd..0ed5e742da 100644 --- a/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" #import "URLType.h" diff --git a/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.m b/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.m index 9835ddfd21..5236a1f97e 100644 --- a/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAQueryRecoveryLinkRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAQueryRecoveryLinkRequestDelegate.h" #import "SVProgressHUD.h" @@ -13,6 +12,7 @@ #import "UIApplication+MNZCategory.h" #import "UITextField+MNZCategory.h" +@import MEGAL10nObjc; @import SAMKeychain; @interface MEGAQueryRecoveryLinkRequestDelegate () @@ -74,12 +74,12 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGAErrorTypeApiEExpired: { NSString *alertTitle; if (MEGALinkManager.urlType == URLTypeCancelAccountLink) { - alertTitle = NSLocalizedString(@"cancellationLinkHasExpired", @"During account cancellation (deletion)"); + alertTitle = LocalizedString(@"cancellationLinkHasExpired", @"During account cancellation (deletion)"); } else if (MEGALinkManager.urlType == URLTypeRecoverLink) { - alertTitle = NSLocalizedString(@"recoveryLinkHasExpired", @"Message shown during forgot your password process if the link to reset password has expired"); + alertTitle = LocalizedString(@"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:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + [linkHasExpiredAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:linkHasExpiredAlertController animated:YES completion:nil]; break; @@ -91,8 +91,8 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } case MEGAErrorTypeApiEAccess: { - UIAlertController *alreadyLoggedInAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"error", nil) message:NSLocalizedString(@"This link is not related to this account. Please log in with the correct account.", @"Error message shown when opening a link with an account that not corresponds to the link") preferredStyle:UIAlertControllerStyleAlert]; - [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDestructive handler:nil]]; + UIAlertController *alreadyLoggedInAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"error", @"") message:LocalizedString(@"This link is not related to this account. Please log in with the correct account.", @"Error message shown when opening a link with an account that not corresponds to the link") preferredStyle:UIAlertControllerStyleAlert]; + [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDestructive handler:nil]]; [UIApplication.mnz_visibleViewController presentViewController:alreadyLoggedInAlertController animated:YES completion:nil]; break; @@ -100,7 +100,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG default: { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; break; } } @@ -113,11 +113,11 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (request.flag) { UIAlertController *masterKeyLoggedInAlertController; if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - masterKeyLoggedInAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") message:NSLocalizedString(@"youRecoveryKeyIsGoingTo", @"Text of the alert after opening the recovery link to reset pass being logged.") preferredStyle:UIAlertControllerStyleAlert]; + masterKeyLoggedInAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") message:LocalizedString(@"youRecoveryKeyIsGoingTo", @"Text of the alert after opening the recovery link to reset pass being logged.") preferredStyle:UIAlertControllerStyleAlert]; } else { - masterKeyLoggedInAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") message:NSLocalizedString(@"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 = [UIAlertController alertControllerWithTitle:LocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") message:LocalizedString(@"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 = NSLocalizedString(@"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.placeholder = LocalizedString(@"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; @@ -125,9 +125,9 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG }]; } - [masterKeyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; + [masterKeyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; - UIAlertAction *okAlertAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertAction *okAlertAction = [UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { NSString *masterKey = masterKeyLoggedInAlertController.textFields.count ? masterKeyLoggedInAlertController.textFields.firstObject.text : MEGASdkManager.sharedMEGASdk.masterKey; [self presentChangeViewType:ChangeTypeResetPassword email:MEGALinkManager.emailOfNewSignUpLink masterKey:masterKey link:request.link]; MEGALinkManager.emailOfNewSignUpLink = nil; diff --git a/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.h b/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.h index 5be83a265c..6ce9c4371a 100644 --- a/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" #import "URLType.h" diff --git a/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.m b/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.m index 4a4f61704a..81dbfb3ad0 100644 --- a/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAQuerySignupLinkRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAQuerySignupLinkRequestDelegate.h" #import "SVProgressHUD.h" @@ -12,6 +11,7 @@ #import "OnboardingViewController.h" #import "UIApplication+MNZCategory.h" +@import MEGAL10nObjc; @import SAMKeychain; @interface MEGAQuerySignupLinkRequestDelegate () @@ -53,8 +53,8 @@ - (void)manageQuerySignupLinkRequest:(MEGARequest *)request { NSString *password = [SAMKeychain passwordForService:@"MEGA" account:@"password"]; [MEGASdkManager.sharedMEGASdk loginWithEmail:request.email password:password delegate:loginRequestDelegate]; } else { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"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:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"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:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { UIViewController *rootViewController = UIApplication.mnz_keyWindow.rootViewController; if ([rootViewController isKindOfClass:OnboardingViewController.class]) { UINavigationController *loginNC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"LoginNavigationControllerID"]; @@ -112,16 +112,16 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGAErrorTypeApiEExpired: case MEGAErrorTypeApiENoent: { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:NSLocalizedString(@"Your confirmation link is no longer valid. Your account may already be activated or you may have cancelled your registration.", nil) preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:LocalizedString(@"Your confirmation link is no longer valid. Your account may already be activated or you may have cancelled your registration.", @"") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; break; } case MEGAErrorTypeApiEAccess: { - UIAlertController *alreadyLoggedInAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"error", nil) message:NSLocalizedString(@"This link is not related to this account. Please log in with the correct account.", @"Error message shown when opening a link with an account that not corresponds to the link") preferredStyle:UIAlertControllerStyleAlert]; - [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDestructive handler:nil]]; + UIAlertController *alreadyLoggedInAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"error", @"") message:LocalizedString(@"This link is not related to this account. Please log in with the correct account.", @"Error message shown when opening a link with an account that not corresponds to the link") preferredStyle:UIAlertControllerStyleAlert]; + [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDestructive handler:nil]]; [UIApplication.mnz_visibleViewController presentViewController:alreadyLoggedInAlertController animated:YES completion:nil]; break; @@ -129,7 +129,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG default: { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; break; } } diff --git a/iMEGA/API/Requests/MEGARemoveContactRequestDelegate.h b/iMEGA/API/Requests/MEGARemoveContactRequestDelegate.h index 5a49095291..d7c5eb9881 100755 --- a/iMEGA/API/Requests/MEGARemoveContactRequestDelegate.h +++ b/iMEGA/API/Requests/MEGARemoveContactRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGARemoveContactRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGARemoveContactRequestDelegate.m b/iMEGA/API/Requests/MEGARemoveContactRequestDelegate.m index a862ba7783..f3e0fef8fd 100755 --- a/iMEGA/API/Requests/MEGARemoveContactRequestDelegate.m +++ b/iMEGA/API/Requests/MEGARemoveContactRequestDelegate.m @@ -1,10 +1,11 @@ - #import "MEGARemoveContactRequestDelegate.h" #import "SVProgressHUD.h" #import "MEGAUser+MNZCategory.h" +@import MEGAL10nObjc; + @interface MEGARemoveContactRequestDelegate () @property (nonatomic, copy) void (^completion)(void); @@ -32,21 +33,21 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; switch (error.type) { case MEGAErrorTypeApiEMasterOnly: { - NSString *status = NSLocalizedString(@"You cannot remove %1$s as a contact because they are part of your Business account.", @"Error shown when a Business account user (sub-user or admin) tries to remove a contact which is part of the same Business account. Please, keep the placeholder, it will be replaced with the name or email of the account, for example: Jane Appleseed or ja@mega.nz"); + NSString *status = LocalizedString(@"You cannot remove %1$s as a contact because they are part of your Business account.", @"Error shown when a Business account user (sub-user or admin) tries to remove a contact which is part of the same Business account. Please, keep the placeholder, it will be replaced with the name or email of the account, for example: Jane Appleseed or ja@mega.nz"); status = [status stringByReplacingOccurrencesOfString:@"%1$s" withString:user.mnz_displayName]; [SVProgressHUD showErrorWithStatus:status]; break; } default: - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; break; } return; } [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - NSString *message = [NSString stringWithFormat:NSLocalizedString(@"removedContact", @"Success message shown when the selected contact has been removed. 'Contact {Name of contact} removed'"), user.mnz_displayName]; + NSString *message = [NSString stringWithFormat:LocalizedString(@"removedContact", @"Success message shown when the selected contact has been removed. 'Contact {Name of contact} removed'"), user.mnz_displayName]; [SVProgressHUD showImage:[UIImage imageNamed:@"hudMinus"] status:message]; if (self.completion) { diff --git a/iMEGA/API/Requests/MEGARemoveRequestDelegate.h b/iMEGA/API/Requests/MEGARemoveRequestDelegate.h index 4c8ddacd4a..92808514a4 100755 --- a/iMEGA/API/Requests/MEGARemoveRequestDelegate.h +++ b/iMEGA/API/Requests/MEGARemoveRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGARemoveRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGARemoveRequestDelegate.m b/iMEGA/API/Requests/MEGARemoveRequestDelegate.m index 7e438d334e..2fb05a0fb9 100755 --- a/iMEGA/API/Requests/MEGARemoveRequestDelegate.m +++ b/iMEGA/API/Requests/MEGARemoveRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGARemoveRequestDelegate.h" #import "SVProgressHUD.h" @@ -6,6 +5,8 @@ #import "DisplayMode.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @interface MEGARemoveRequestDelegate () @property (nonatomic) DisplayMode mode; @@ -42,7 +43,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (error.type) { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; return; } @@ -55,9 +56,9 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [SVProgressHUD showImage:[UIImage imageNamed:@"hudMinus"] status:message]; } else if (self.mode == DisplayModeSharedItem) { if (self.totalRequests > 1) { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"sharesLeft", @"Message shown when some shares have been left")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"sharesLeft", @"Message shown when some shares have been left")]; } else { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"shareLeft", @"Message shown when a share has been left")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"shareLeft", @"Message shown when a share has been left")]; } } diff --git a/iMEGA/API/Requests/MEGASetAttrUserRequestDelegate.h b/iMEGA/API/Requests/MEGASetAttrUserRequestDelegate.h index 01d2e17fdc..3b26ee2e6f 100644 --- a/iMEGA/API/Requests/MEGASetAttrUserRequestDelegate.h +++ b/iMEGA/API/Requests/MEGASetAttrUserRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGASetAttrUserRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGASetAttrUserRequestDelegate.m b/iMEGA/API/Requests/MEGASetAttrUserRequestDelegate.m index 8c4519151e..ec33afe579 100644 --- a/iMEGA/API/Requests/MEGASetAttrUserRequestDelegate.m +++ b/iMEGA/API/Requests/MEGASetAttrUserRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGASetAttrUserRequestDelegate.h" @interface MEGASetAttrUserRequestDelegate () diff --git a/iMEGA/API/Requests/MEGAShareRequestDelegate.h b/iMEGA/API/Requests/MEGAShareRequestDelegate.h index d0fa8e58df..445402cf57 100755 --- a/iMEGA/API/Requests/MEGAShareRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAShareRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGAShareRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGAShareRequestDelegate.m b/iMEGA/API/Requests/MEGAShareRequestDelegate.m index 58ed45aefd..c5dfe76934 100755 --- a/iMEGA/API/Requests/MEGAShareRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAShareRequestDelegate.m @@ -1,7 +1,7 @@ - #import "MEGAShareRequestDelegate.h" #import "SVProgressHUD.h" +@import MEGAL10nObjc; @interface MEGAShareRequestDelegate () @@ -48,7 +48,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (error.type) { if (error.type != MEGAErrorTypeApiEBusinessPastDue) { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; } else { [SVProgressHUD dismiss]; } @@ -61,19 +61,19 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (self.isChangingPermissions) { if (request.access == MEGAShareTypeAccessUnknown) { if (self.totalRequests > 1) { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudForbidden"] status:NSLocalizedString(@"sharesRemoved", @"Message shown when some shares have been removed")]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudForbidden"] status:LocalizedString(@"sharesRemoved", @"Message shown when some shares have been removed")]; } else { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudForbidden"] status:NSLocalizedString(@"shareRemoved", @"Message shown when a share have been removed")]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudForbidden"] status:LocalizedString(@"shareRemoved", @"Message shown when a share have been removed")]; } } else { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"permissionsChanged", @"Message shown when you have changed the permissions of a shared folder")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"permissionsChanged", @"Message shown when you have changed the permissions of a shared folder")]; } } else { if (self.totalRequests > 1) { - NSString *sharedFolders = [NSString stringWithFormat:NSLocalizedString(@"sharedFolders_success", @"Success message for sharing multiple files."), self.totalRequests]; + NSString *sharedFolders = [NSString stringWithFormat:LocalizedString(@"sharedFolders_success", @"Success message for sharing multiple files."), self.totalRequests]; [SVProgressHUD showImage:[UIImage imageNamed:@"hudSharedFolder"] status:sharedFolders]; } else { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudSharedFolder"] status:NSLocalizedString(@"sharedFolder_success", @"Message shown when a folder have been shared")]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudSharedFolder"] status:LocalizedString(@"sharedFolder_success", @"Message shown when a folder have been shared")]; } } diff --git a/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.h b/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.h index 17c65a2983..5937b43430 100644 --- a/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.h +++ b/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.h @@ -1,4 +1,3 @@ - #import "MEGARequestDelegate.h" @interface MEGAShowPasswordReminderRequestDelegate : NSObject diff --git a/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.m b/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.m index 55523c7336..d45fb6c78e 100644 --- a/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.m +++ b/iMEGA/API/Requests/MEGAShowPasswordReminderRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAShowPasswordReminderRequestDelegate.h" #import "MEGANavigationController.h" diff --git a/iMEGA/API/Transfers/MEGAPauseTransferRequestDelegate.h b/iMEGA/API/Transfers/MEGAPauseTransferRequestDelegate.h index 1f43fc5b4f..a9997b2a2b 100644 --- a/iMEGA/API/Transfers/MEGAPauseTransferRequestDelegate.h +++ b/iMEGA/API/Transfers/MEGAPauseTransferRequestDelegate.h @@ -1,6 +1,3 @@ - - - @interface MEGAPauseTransferRequestDelegate : NSObject - (id)init NS_UNAVAILABLE; diff --git a/iMEGA/API/Transfers/MEGAPauseTransferRequestDelegate.m b/iMEGA/API/Transfers/MEGAPauseTransferRequestDelegate.m index 6097db0a56..9db0064a57 100644 --- a/iMEGA/API/Transfers/MEGAPauseTransferRequestDelegate.m +++ b/iMEGA/API/Transfers/MEGAPauseTransferRequestDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAPauseTransferRequestDelegate.h" @interface MEGAPauseTransferRequestDelegate () diff --git a/iMEGA/API/Transfers/MEGAStartDownloadTransferDelegate.h b/iMEGA/API/Transfers/MEGAStartDownloadTransferDelegate.h index 6017e6c24f..baa702254c 100644 --- a/iMEGA/API/Transfers/MEGAStartDownloadTransferDelegate.h +++ b/iMEGA/API/Transfers/MEGAStartDownloadTransferDelegate.h @@ -1,4 +1,3 @@ - #import @interface MEGAStartDownloadTransferDelegate : NSObject diff --git a/iMEGA/API/Transfers/MEGAStartDownloadTransferDelegate.m b/iMEGA/API/Transfers/MEGAStartDownloadTransferDelegate.m index 12fb0576ea..96c38b232b 100644 --- a/iMEGA/API/Transfers/MEGAStartDownloadTransferDelegate.m +++ b/iMEGA/API/Transfers/MEGAStartDownloadTransferDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAStartDownloadTransferDelegate.h" @interface MEGAStartDownloadTransferDelegate () diff --git a/iMEGA/API/Transfers/MEGAStartUploadTransferDelegate.h b/iMEGA/API/Transfers/MEGAStartUploadTransferDelegate.h index 5caf1a5f60..f234dde272 100755 --- a/iMEGA/API/Transfers/MEGAStartUploadTransferDelegate.h +++ b/iMEGA/API/Transfers/MEGAStartUploadTransferDelegate.h @@ -1,4 +1,3 @@ - #import "MEGAStartUploadTransferDelegate.h" @interface MEGAStartUploadTransferDelegate : NSObject diff --git a/iMEGA/API/Transfers/MEGAStartUploadTransferDelegate.m b/iMEGA/API/Transfers/MEGAStartUploadTransferDelegate.m index 667d1219d3..eb16423b86 100755 --- a/iMEGA/API/Transfers/MEGAStartUploadTransferDelegate.m +++ b/iMEGA/API/Transfers/MEGAStartUploadTransferDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAStartUploadTransferDelegate.h" @interface MEGAStartUploadTransferDelegate () diff --git a/iMEGA/AppDelegate/AppDelegate+Account.swift b/iMEGA/AppDelegate/AppDelegate+Account.swift index 51d4cfb200..e873d8fa2d 100644 --- a/iMEGA/AppDelegate/AppDelegate+Account.swift +++ b/iMEGA/AppDelegate/AppDelegate+Account.swift @@ -1,8 +1,9 @@ import MEGADomain +import MEGAL10n extension AppDelegate { @objc func expiredAccountTitle() -> String { - guard let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails else { + guard let accountDetails = MEGASdk.shared.mnz_accountDetails else { return "" } switch accountDetails.type { @@ -14,14 +15,14 @@ extension AppDelegate { } @objc func expiredAccountMessage() -> String { - guard let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails else { + guard let accountDetails = MEGASdk.shared.mnz_accountDetails else { return "" } switch accountDetails.type { case .proFlexi: return Strings.Localizable.Account.Expired.ProFlexi.message default: - if MEGASdkManager.sharedMEGASdk().isMasterBusinessAccount { + if MEGASdk.shared.isMasterBusinessAccount { return Strings.Localizable.ThereHasBeenAProblemProcessingYourPayment.megaIsLimitedToViewOnlyUntilThisIssueHasBeenFixedInADesktopWebBrowser } else { let message = Strings.Localizable.YourAccountIsCurrentlyBSuspendedB.youCanOnlyBrowseYourData diff --git a/iMEGA/AppDelegate/AppDelegate+Additions.swift b/iMEGA/AppDelegate/AppDelegate+Additions.swift index 04412e24e9..1257a232f2 100644 --- a/iMEGA/AppDelegate/AppDelegate+Additions.swift +++ b/iMEGA/AppDelegate/AppDelegate+Additions.swift @@ -1,6 +1,7 @@ import Combine import Foundation import MEGADomain +import MEGAL10n import MEGAPermissions import MEGAPresentation import MEGASDKRepo @@ -12,16 +13,20 @@ extension AppDelegate { return } - MEGASdkManager.sharedMEGASdk().multiFactorAuthCheck(withEmail: MEGASdk.currentUserEmail ?? "", delegate: MEGAGenericRequestDelegate.init(completion: { (request, _) in - if request.flag { - return // Two Factor Authentication Enabled + MEGASdk.shared.multiFactorAuthCheck(withEmail: MEGASdk.currentUserEmail ?? "", delegate: RequestDelegate { result in + switch result { + case .success(let request): + if request.flag { + return // Two Factor Authentication Enabled + } + case .failure: + break } - if UIApplication.mnz_visibleViewController() is AddPhoneNumberViewController || UIApplication.mnz_visibleViewController() is CustomModalAlertViewController || UIApplication.mnz_visibleViewController() is AccountExpiredViewController || - (MEGASdkManager.sharedMEGASdk().isAccountType(.business) && - MEGASdkManager.sharedMEGASdk().businessStatus != .active) { + (MEGASdk.shared.isAccountType(.business) && + MEGASdk.shared.businessStatus != .active) { return } @@ -35,7 +40,7 @@ extension AppDelegate { UIApplication.mnz_presentingViewController().present(enable2FACustomModalAlert, animated: true, completion: nil) UserDefaults.standard.set(true, forKey: "twoFactorAuthenticationAlreadySuggested") - })) + }) } private var permissionHandler: any DevicePermissionsHandling { @@ -84,7 +89,7 @@ extension AppDelegate { } @objc func performCall(presenter: UIViewController, chatRoom: MEGAChatRoom, isSpeakerEnabled: Bool) { - guard let call = MEGASdkManager.sharedMEGAChatSdk().chatCall(forChatId: chatRoom.chatId) else { return } + guard let call = MEGAChatSdk.shared.chatCall(forChatId: chatRoom.chatId) else { return } MeetingContainerRouter(presenter: presenter, chatRoom: chatRoom.toChatRoomEntity(), call: call.toCallEntity(), @@ -127,7 +132,7 @@ extension AppDelegate { } @objc func updateContactsNickname() { - MEGASdkManager.sharedMEGASdk().getUserAttributeType(.alias, delegate: RequestDelegate { (result) in + MEGASdk.shared.getUserAttributeType(.alias, delegate: RequestDelegate { (result) in if case let .success(request) = result { guard let stringDictionary = request.megaStringDictionary else { return } @@ -148,7 +153,7 @@ extension AppDelegate { @objc func handleAccountBlockedEvent(_ event: MEGAEvent) { guard let suspensionType = AccountSuspensionType(rawValue: event.number) else { return } - if suspensionType == .smsVerification && MEGASdkManager.sharedMEGASdk().smsAllowedState() != .notAllowed { + if suspensionType == .smsVerification && MEGASdk.shared.smsAllowedState() != .notAllowed { if UIApplication.mnz_presentingViewController() is SMSNavigationViewController { return } @@ -179,7 +184,7 @@ extension AppDelegate { let alert = UIAlertController(title: Strings.Localizable.error, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: Strings.Localizable.ok, style: .cancel) { _ in - MEGASdkManager.sharedMEGASdk().logout() + MEGASdk.shared.logout() }) UIApplication.mnz_presentingViewController().present(alert, animated: true, completion: nil) } @@ -240,7 +245,9 @@ extension AppDelegate { default: CrashlyticsLogger.log("MEGAChatSDK onDBError occurred. Error \(error) with message \(message)") - MEGASdkManager.deleteSharedSdks() + MEGAChatSdk.shared.deleteMegaChatApi() + MEGASdk.shared.deleteMegaApi() + MEGASdk.sharedFolderLink.deleteMegaApi() exit(0) } } @@ -276,7 +283,7 @@ extension AppDelegate { private func enableLogs() { MEGASdk.setLogLevel(.max) MEGAChatSdk.setLogLevel(.max) - MEGASdkManager.sharedMEGASdk().add(Logger.shared()) + MEGASdk.shared.add(Logger.shared()) MEGAChatSdk.setLogObject(Logger.shared()) } @@ -284,7 +291,7 @@ extension AppDelegate { let logUseCase = LogUseCase(preferenceUseCase: PreferenceUseCase.default, appEnvironment: AppEnvironmentUseCase.shared) if logUseCase.shouldEnableLogs() { - MEGASdkManager.sharedMEGASdk().remove(Logger.shared()) + MEGASdk.shared.remove(Logger.shared()) } } } @@ -381,7 +388,7 @@ extension AppDelegate { return } - performCall(presenter: UIApplication.mnz_presentingViewController(), chatRoom: chatRoom, isSpeakerEnabled: AVAudioSession.sharedInstance().mnz_isOutputEqual(toPortType: .builtInSpeaker)) + performCall(presenter: UIApplication.mnz_presentingViewController(), chatRoom: chatRoom, isSpeakerEnabled: AVAudioSession.sharedInstance().isOutputEqualToPortType(.builtInSpeaker)) } @objc func registerCustomActionsForStartScheduledMeetingNotification() { @@ -399,11 +406,13 @@ extension AppDelegate { audioSessionUC.configureCallAudioSession() audioSessionUC.enableLoudSpeaker() - let scheduledMeetingUseCase = ScheduledMeetingUseCase(repository: ScheduledMeetingRepository(chatSDK: MEGASdkManager.sharedMEGAChatSdk())) - let callUseCase = CallUseCase(repository: CallRepository(chatSdk: MEGASdkManager.sharedMEGAChatSdk(), callActionManager: CallActionManager.shared)) + let scheduledMeetingUseCase = ScheduledMeetingUseCase(repository: ScheduledMeetingRepository(chatSDK: .shared)) + let callUseCase = CallUseCase(repository: CallRepository(chatSdk: .shared, callActionManager: CallActionManager.shared)) if let scheduleMeeting = scheduledMeetingUseCase.scheduledMeetingsByChat(chatId: chatRoom.chatId).first { - let callEntity = try await callUseCase.startCallNoRinging(for: scheduleMeeting, enableVideo: false, enableAudio: true) + let callEntity = chatRoom.isWaitingRoomEnabled ? + try await callUseCase.startMeetingInWaitingRoomChat(for: scheduleMeeting, enableVideo: false, enableAudio: true) : + try await callUseCase.startCallNoRinging(for: scheduleMeeting, enableVideo: false, enableAudio: true) join(call: callEntity, chatRoom: chatRoom.toChatRoomEntity()) } else { let callEntity = try await callUseCase.startCall(for: chatRoom.chatId, enableVideo: false, enableAudio: true) @@ -477,4 +486,9 @@ extension AppDelegate { @objc func showChooseAccountPlanTypeView() { UpgradeAccountRouter().presentChooseAccountType() } + + // MARK: - Promoted plan + @objc func listenToStorePaymentTransactions() { + SKPaymentQueue.default().add(MEGAPurchase.sharedInstance()) + } } diff --git a/iMEGA/AppDelegate/AppDelegate.m b/iMEGA/AppDelegate/AppDelegate.m index 6d9c97027a..9c7a48df0f 100644 --- a/iMEGA/AppDelegate/AppDelegate.m +++ b/iMEGA/AppDelegate/AppDelegate.m @@ -48,9 +48,10 @@ #import #import "MEGASdkManager+CleanUp.h" @import Firebase; -@import SDWebImageWebPCoder; +@import MEGAL10nObjc; @import MEGASDKRepo; - +@import SDWebImageWebPCoder; +@import MEGAFoundation; #import "MEGA-Swift.h" @interface AppDelegate () { @@ -203,8 +204,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( if (chatInit == MEGAChatInitError) { MEGALogError(@"Init Karere with session failed"); NSString *message = [NSString stringWithFormat:@"Error (%ld) initializing the chat", (long)chatInit]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"error", nil) message:message preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"error", @"nil") message:message preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [[MEGASdkManager sharedMEGAChatSdk] logout]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } else if (chatInit == MEGAChatInitOnlineSession || chatInit == MEGAChatInitOfflineSession) { @@ -222,7 +223,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [[LTHPasscodeViewController sharedUser] showLockScreenWithAnimation:NO withLogout:YES - andLogoutTitle:NSLocalizedString(@"logoutLabel", nil)]; + andLogoutTitle:LocalizedString(@"logoutLabel", @"")]; [self.window setRootViewController:[LTHPasscodeViewController sharedUser]]; } else { _mainTBC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"TabBarControllerID"]; @@ -247,6 +248,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( }]; createAccountRequestDelegate.resumeCreateAccount = YES; [[MEGASdkManager sharedMEGASdk] resumeCreateAccountWithSessionId:sessionId delegate:createAccountRequestDelegate]; + } else { + [self listenToStorePaymentTransactions]; } } @@ -430,7 +433,7 @@ - (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); - BOOL isSpeakerEnabled = [AVAudioSession.sharedInstance mnz_isOutputEqualToPortType:AVAudioSessionPortBuiltInSpeaker]; + BOOL isSpeakerEnabled = [AVAudioSession.sharedInstance isOutputEqualToPortType:AVAudioSessionPortBuiltInSpeaker]; [self performCallWithPresenter:UIApplication.mnz_presentingViewController chatRoom:self.chatRoom isSpeakerEnabled:isSpeakerEnabled]; @@ -463,7 +466,7 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct self.chatRoom = [[MEGASdkManager sharedMEGAChatSdk] chatRoomForChatId:call.chatId]; MEGALogDebug(@"call id %llu", call.callId); MEGALogDebug(@"There is a call in progress for this chat %@", call); - BOOL isSpeakerEnabled = [AVAudioSession.sharedInstance mnz_isOutputEqualToPortType:AVAudioSessionPortBuiltInSpeaker]; + BOOL isSpeakerEnabled = [AVAudioSession.sharedInstance isOutputEqualToPortType:AVAudioSessionPortBuiltInSpeaker]; [self performCallWithPresenter:UIApplication.mnz_presentingViewController chatRoom:self.chatRoom isSpeakerEnabled:isSpeakerEnabled]; self.chatRoom = nil; } else { @@ -636,29 +639,6 @@ - (BOOL)manageQuickActionType:(NSString *)type { return quickActionManaged; } -- (void)requestUserName { - NSNumber *handle = MEGASdk.currentUserHandle; - if (handle == nil) { - return; - } - - if (![[MEGAStore shareInstance] fetchUserWithUserHandle:[handle unsignedLongLongValue]]) { - [[MEGASdkManager sharedMEGASdk] getUserAttributeType:MEGAUserAttributeFirstname]; - [[MEGASdkManager sharedMEGASdk] getUserAttributeType:MEGAUserAttributeLastname]; - } -} - -- (void)requestContactsFullname { - MEGAUserList *userList = [[MEGASdkManager sharedMEGASdk] contacts]; - for (NSInteger i = 0; i < userList.size.integerValue; i++) { - MEGAUser *user = [userList userAtIndex:i]; - if (![[MEGAStore shareInstance] fetchUserWithUserHandle:user.handle] && user.visibility == MEGAUserVisibilityVisible) { - [[MEGASdkManager sharedMEGASdk] getUserAttributeForUser:user type:MEGAUserAttributeFirstname]; - [[MEGASdkManager sharedMEGASdk] getUserAttributeForUser:user type:MEGAUserAttributeLastname]; - } - } -} - - (void)showMainTabBar { if (![self.window.rootViewController isKindOfClass:[LTHPasscodeViewController class]]) { @@ -672,7 +652,7 @@ - (void)showMainTabBar { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"presentPasscodeLater"]) { [[LTHPasscodeViewController sharedUser] showLockScreenWithAnimation:NO withLogout:YES - andLogoutTitle:NSLocalizedString(@"logoutLabel", nil)]; + andLogoutTitle:LocalizedString(@"logoutLabel", @"")]; } } } @@ -884,7 +864,7 @@ - (void)performCall { - (void)presentInviteContactCustomAlertViewController { BOOL isInOutgoingContactRequest = NO; MEGAContactRequestList *outgoingContactRequestList = [[MEGASdkManager sharedMEGASdk] outgoingContactRequests]; - for (NSInteger i = 0; i < [[outgoingContactRequestList size] integerValue]; i++) { + for (NSInteger i = 0; i < outgoingContactRequestList.size; i++) { MEGAContactRequest *contactRequest = [outgoingContactRequestList contactRequestAtIndex:i]; if ([self.email isEqualToString:contactRequest.targetEmail]) { isInOutgoingContactRequest = YES; @@ -998,7 +978,7 @@ - (void)presentAccountExpiredAlertIfNeeded { NSString *alertMessage = [self expiredAccountMessage]; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:alertTitle message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"dismiss", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"dismiss", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { self.accountExpiredPresented = NO; }]]; @@ -1027,8 +1007,8 @@ - (void)presentAccountExpiredViewIfNeeded { } - (void)presentLogoutFromOtherClientAlert { - self.API_ESIDAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"loggedOut_alertTitle", nil) message:NSLocalizedString(@"loggedOutFromAnotherLocation", nil) preferredStyle:UIAlertControllerStyleAlert]; - [self.API_ESIDAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + self.API_ESIDAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"loggedOut_alertTitle", @"") message:LocalizedString(@"loggedOutFromAnotherLocation", @"") preferredStyle:UIAlertControllerStyleAlert]; + [self.API_ESIDAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:self.API_ESIDAlertController animated:YES completion:nil]; } @@ -1166,6 +1146,7 @@ - (void)setupFinished { return; } [self showMainTabBar]; + [[MEGAPurchase sharedInstance] processAnyPendingPromotedPlanPayment]; } - (void)readyToShowRecommendations { @@ -1261,7 +1242,8 @@ - (void)onUsersUpdate:(MEGASdk *)sdk userList:(MEGAUserList *)userList { - (void)onNodesUpdate:(MEGASdk *)api nodeList:(MEGANodeList *)nodeList { if (nodeList) { [self.quickAccessWidgetManager createQuickAccessWidgetItemsDataIfNeededFor:nodeList]; - + [self.quickAccessWidgetManager updateFavouritesWidgetFor:nodeList]; + [self postNodeUpdatesNotificationsFor:nodeList]; } else { [Helper startPendingUploadTransferIfNeeded]; @@ -1357,7 +1339,7 @@ - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { } if (request.paramType != MEGAErrorTypeApiESSL && request.flag) { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudLogOut"] status:NSLocalizedString(@"loggingOut", @"String shown when you are logging out of your account.")]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudLogOut"] status:LocalizedString(@"loggingOut", @"String shown when you are logging out of your account.")]; } break; } @@ -1384,8 +1366,8 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [self showOnboardingWithCompletion:^{ if (MEGALinkManager.urlType == URLTypeCancelAccountLink) { - UIAlertController *accountCanceledSuccessfullyAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"accountCanceledSuccessfully", @"During account cancellation (deletion)") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [accountCanceledSuccessfullyAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *accountCanceledSuccessfullyAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"accountCanceledSuccessfully", @"During account cancellation (deletion)") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [accountCanceledSuccessfullyAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:accountCanceledSuccessfullyAlertController animated:YES completion:^{ [MEGALinkManager resetLinkAndURLType]; }]; @@ -1410,8 +1392,8 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if ([api isForeignNode:request.parentHandle]) { if (![UIApplication.mnz_presentingViewController isKindOfClass:UIAlertController.class]) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:NSLocalizedString(@"dialog.shareOwnerStorageQuota.message", nil) preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:LocalizedString(@"dialog.shareOwnerStorageQuota.message", @"") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } } else { @@ -1434,19 +1416,19 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGAErrorTypeApiEIncomplete: { if (request.type == MEGARequestTypeLogout && request.paramType == MEGAErrorTypeApiESSL && !self.sslKeyPinningController) { [SVProgressHUD dismiss]; - _sslKeyPinningController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"sslUnverified_alertTitle", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; - [self.sslKeyPinningController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ignore", @"Button title to allow the user ignore something") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + _sslKeyPinningController = [UIAlertController alertControllerWithTitle:LocalizedString(@"sslUnverified_alertTitle", @"") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [self.sslKeyPinningController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ignore", @"Button title to allow the user ignore something") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { self.sslKeyPinningController = nil; [api setPublicKeyPinning:NO]; [api reconnect]; }]]; - [self.sslKeyPinningController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"retry", @"Button which allows to retry send message in chat conversation.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [self.sslKeyPinningController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"retry", @"Button which allows to retry send message in chat conversation.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { self.sslKeyPinningController = nil; [api retryPendingConnections]; }]]; - [self.sslKeyPinningController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"openBrowser", @"Button title to allow the user open the default browser") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [self.sslKeyPinningController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"openBrowser", @"Button title to allow the user open the default browser") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { self.sslKeyPinningController = nil; NSURL *url = [NSURL URLWithString:@"https://mega.nz"]; [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:NULL]; @@ -1510,6 +1492,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGARequestTypeFetchNodes: { [self postDidFinishFetchNodesNotification]; + [self listenToStorePaymentTransactions]; [[SKPaymentQueue defaultQueue] addTransactionObserver:[MEGAPurchase sharedInstance]]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"TransfersPaused"]) { @@ -1522,8 +1505,6 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [self requestUserName]; - [self requestContactsFullname]; [self updateContactsNickname]; MEGAChatNotificationDelegate *chatNotificationDelegate = MEGAChatNotificationDelegate.new; @@ -1796,9 +1777,9 @@ - (void)onTransferFinish:(MEGASdk *)sdk transfer:(MEGATransfer *)transfer error: break; default: { if (error.type != MEGAErrorTypeApiESid && error.type != MEGAErrorTypeApiESSL && error.type != MEGAErrorTypeApiEExist && error.type != MEGAErrorTypeApiEIncomplete) { - NSString *transferFailed = NSLocalizedString(@"Transfer failed:", @"Notification message shown when a transfer failed. Keep colon."); + NSString *transferFailed = LocalizedString(@"Transfer failed:", @"Notification message shown when a transfer failed. Keep colon."); NSString *errorString = [MEGAError errorStringWithErrorCode:error.type context:(transfer.type == MEGATransferTypeUpload) ? MEGAErrorContextUpload : MEGAErrorContextDownload]; - MEGALogError(@"%@\n%@ %@", transfer.fileName, transferFailed, NSLocalizedString(errorString, nil)); + MEGALogError(@"%@\n%@ %@", transfer.fileName, transferFailed, LocalizedString(errorString, @"")); } break; } diff --git a/iMEGA/AudioPlayer/AudioPlayer/AudioPlayer+MetadataLoader.swift b/iMEGA/AudioPlayer/AudioPlayer/AudioPlayer+MetadataLoader.swift index 589f71a30c..64423add45 100644 --- a/iMEGA/AudioPlayer/AudioPlayer/AudioPlayer+MetadataLoader.swift +++ b/iMEGA/AudioPlayer/AudioPlayer/AudioPlayer+MetadataLoader.swift @@ -28,9 +28,9 @@ extension AudioPlayer: AudioPlayerMetadataLoaderProtocol { final class AudioPlayerMetadataOperation: MEGAOperation { private let item: AudioPlayerItem - private let completion: (Result) -> Void + private let completion: (Result) -> Void - init(item: AudioPlayerItem, completion: @escaping (Result) -> Void) { + init(item: AudioPlayerItem, completion: @escaping (Result) -> Void) { self.item = item self.completion = completion } @@ -44,7 +44,7 @@ final class AudioPlayerMetadataOperation: MEGAOperation { loadItemMetadata() } - private func finishOperation(error: Error?) { + private func finishOperation(error: (any Error)?) { if let error = error { completion(.failure(error)) } else { diff --git a/iMEGA/AudioPlayer/AudioPlayer/AudioPlayerManager.swift b/iMEGA/AudioPlayer/AudioPlayer/AudioPlayerManager.swift index c84971da83..f00e223b9c 100644 --- a/iMEGA/AudioPlayer/AudioPlayer/AudioPlayerManager.swift +++ b/iMEGA/AudioPlayer/AudioPlayer/AudioPlayerManager.swift @@ -166,7 +166,7 @@ import MEGAPresentation } private func checkIfCallExist(then clousure: (() -> Void)?) { - if MEGASdkManager.sharedMEGAChatSdk().mnz_existsActiveCall { + if MEGAChatSdk.shared.mnz_existsActiveCall { Helper.cannotPlayContentDuringACallAlert() } else { clousure?() @@ -263,8 +263,47 @@ import MEGAPresentation func initFullScreenPlayer(node: MEGANode?, fileLink: String?, filePaths: [String]?, isFolderLink: Bool, presenter: UIViewController, messageId: HandleEntity, chatId: HandleEntity, allNodes: [MEGANode]?) { let configEntity = AudioPlayerConfigEntity(node: node, isFolderLink: isFolderLink, fileLink: fileLink, messageId: messageId, chatId: chatId, relatedFiles: filePaths, allNodes: allNodes, playerHandler: self) - AudioPlayerViewRouter(configEntity: configEntity, presenter: presenter) - .start() + let playlistRouter = AudioPlaylistViewRouter(configEntity: AudioPlayerConfigEntity(parentNode: configEntity.node?.parent, playerHandler: configEntity.playerHandler), presenter: presenter) + + let router = AudioPlayerViewRouter( + configEntity: configEntity, + presenter: presenter, + audioPlaylistViewRouter: playlistRouter + ) + + guard let vc = makeAudioPlayerViewController(configEntity: configEntity, router: router) else { return } + router.baseViewController = vc + + router.start() + } + + private func makeAudioPlayerViewController(configEntity: AudioPlayerConfigEntity, router: some AudioPlayerViewRouting) -> AudioPlayerViewController? { + guard let vc = UIStoryboard(name: "AudioPlayer", bundle: nil).instantiateViewController(identifier: "AudioPlayerViewControllerID", creator: { coder in + let makeViewModel: () -> AudioPlayerViewModel = { + if configEntity.playerType == .offline { + return AudioPlayerViewModel( + configEntity: configEntity, + router: router, + offlineInfoUseCase: OfflineFileInfoUseCase(offlineInfoRepository: OfflineInfoRepository()), + playbackContinuationUseCase: DIContainer.playbackContinuationUseCase + ) + } else { + return AudioPlayerViewModel( + configEntity: configEntity, + router: router, + nodeInfoUseCase: NodeInfoUseCase(nodeInfoRepository: NodeInfoRepository()), + streamingInfoUseCase: StreamingInfoUseCase(streamingInfoRepository: StreamingInfoRepository()), + playbackContinuationUseCase: DIContainer.playbackContinuationUseCase + ) + } + } + + return AudioPlayerViewController(coder: coder, viewModel: makeViewModel()) + }) as? AudioPlayerViewController else { + return nil + } + + return vc } func initMiniPlayer(node: MEGANode?, fileLink: String?, filePaths: [String]?, isFolderLink: Bool, presenter: UIViewController, shouldReloadPlayerInfo: Bool, shouldResetPlayer: Bool) { diff --git a/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerFileToolbarConfigurator.swift b/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerFileToolbarConfigurator.swift index 3b86c86b1d..6d203bf0bb 100644 --- a/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerFileToolbarConfigurator.swift +++ b/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerFileToolbarConfigurator.swift @@ -1,4 +1,3 @@ - final class AudioPlayerFileToolbarConfigurator { typealias ButtonAction = (UIBarButtonItem) -> Void let importAction: ButtonAction diff --git a/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewController.swift b/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewController.swift index 1c653097a3..7e9352ab39 100644 --- a/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewController.swift +++ b/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit enum PlayerViewType { @@ -41,7 +42,16 @@ final class AudioPlayerViewController: UIViewController { private var playerType: PlayerType = .default // MARK: - Internal properties - var viewModel: AudioPlayerViewModel! + private(set) var viewModel: AudioPlayerViewModel + + init?(coder: NSCoder, viewModel: AudioPlayerViewModel) { + self.viewModel = viewModel + super.init(coder: coder) + } + + required init?(coder: NSCoder) { + fatalError("You must create this view controller with a user.") + } // MARK: - View lifecycle override func viewDidLoad() { diff --git a/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewModel.swift b/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewModel.swift index 67308fc1c0..89992d874f 100644 --- a/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewModel.swift +++ b/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewModel.swift @@ -1,6 +1,7 @@ import Foundation import MEGADomain import MEGAFoundation +import MEGAL10n import MEGAPresentation enum AudioPlayerAction: ActionType { diff --git a/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewRouter.swift b/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewRouter.swift index 9463d713d3..576785c522 100644 --- a/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewRouter.swift +++ b/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewRouter.swift @@ -3,40 +3,24 @@ import MEGADomain import MEGAPresentation final class AudioPlayerViewRouter: NSObject, AudioPlayerViewRouting { - private weak var baseViewController: UIViewController? - private weak var presenter: UIViewController? - private var configEntity: AudioPlayerConfigEntity + private let configEntity: AudioPlayerConfigEntity + private let presenter: UIViewController + private let audioPlaylistViewRouter: any AudioPlaylistViewRouting + private(set) var nodeActionViewControllerDelegate: NodeActionViewControllerGenericDelegate? private(set) var fileLinkActionViewControllerDelegate: FileLinkActionViewControllerDelegate? - init(configEntity: AudioPlayerConfigEntity, presenter: UIViewController) { + var baseViewController: UIViewController? + + init(configEntity: AudioPlayerConfigEntity, presenter: UIViewController, audioPlaylistViewRouter: some AudioPlaylistViewRouting) { self.configEntity = configEntity self.presenter = presenter + self.audioPlaylistViewRouter = audioPlaylistViewRouter super.init() } func build() -> UIViewController { - let vc = UIStoryboard(name: "AudioPlayer", bundle: nil) - .instantiateViewController(withIdentifier: "AudioPlayerViewControllerID") as! AudioPlayerViewController - - if configEntity.playerType == .offline { - vc.viewModel = AudioPlayerViewModel( - configEntity: configEntity, - router: self, - offlineInfoUseCase: OfflineFileInfoUseCase(offlineInfoRepository: OfflineInfoRepository()), - playbackContinuationUseCase: DIContainer.playbackContinuationUseCase - ) - } else { - vc.viewModel = AudioPlayerViewModel( - configEntity: configEntity, - router: self, - nodeInfoUseCase: NodeInfoUseCase(nodeInfoRepository: NodeInfoRepository()), - streamingInfoUseCase: StreamingInfoUseCase(streamingInfoRepository: StreamingInfoRepository()), - playbackContinuationUseCase: DIContainer.playbackContinuationUseCase - ) - } - - baseViewController = vc + guard let vc = baseViewController else { return UIViewController() } switch configEntity.nodeOriginType { case .folderLink, .chat: @@ -59,7 +43,7 @@ final class AudioPlayerViewRouter: NSObject, AudioPlayerViewRouting { } @objc func start() { - presenter?.present(build(), animated: true, completion: nil) + presenter.present(build(), animated: true, completion: nil) } // MARK: - UI Actions @@ -68,19 +52,14 @@ final class AudioPlayerViewRouter: NSObject, AudioPlayerViewRouting { } func goToPlaylist() { - guard let presenter = self.baseViewController else { return } - AudioPlaylistViewRouter(configEntity: AudioPlayerConfigEntity(parentNode: configEntity.node?.parent, playerHandler: configEntity.playerHandler), presenter: presenter).start() + audioPlaylistViewRouter.start() } func showMiniPlayer(node: MEGANode?, shouldReload: Bool) { - guard let presenter = presenter else { return } - configEntity.playerHandler.initMiniPlayer(node: node, fileLink: configEntity.fileLink, filePaths: configEntity.relatedFiles, isFolderLink: configEntity.isFolderLink, presenter: presenter, shouldReloadPlayerInfo: shouldReload, shouldResetPlayer: false) } func showMiniPlayer(file: String, shouldReload: Bool) { - guard let presenter = presenter else { return } - configEntity.playerHandler.initMiniPlayer(node: nil, fileLink: file, filePaths: configEntity.relatedFiles, isFolderLink: configEntity.isFolderLink, presenter: presenter, shouldReloadPlayerInfo: shouldReload, shouldResetPlayer: false) } @@ -102,7 +81,11 @@ final class AudioPlayerViewRouter: NSObject, AudioPlayerViewRouting { let isBackupNode = backupsUC.isBackupNode(node.toNodeEntity()) let nodeActionViewController = NodeActionViewController( node: node, - delegate: self, + delegate: AudioPlayerViewRouterNodeActionAdapter( + configEntity: configEntity, + nodeActionViewControllerDelegate: nodeActionViewControllerDelegate, + fileLinkActionViewControllerDelegate: fileLinkActionViewControllerDelegate + ), displayMode: displayMode, isInVersionsView: isPlayingFromVersionView(), isBackupNode: isBackupNode, @@ -112,20 +95,6 @@ final class AudioPlayerViewRouter: NSObject, AudioPlayerViewRouting { } private func isPlayingFromVersionView() -> Bool { - return presenter?.isKind(of: NodeVersionsViewController.self) == true - } -} - -extension AudioPlayerViewRouter: NodeActionViewControllerDelegate { - - func nodeAction(_ nodeAction: NodeActionViewController, didSelect action: MegaNodeActionType, for node: MEGANode, from sender: Any) { - switch configEntity.nodeOriginType { - case .folderLink, .chat: - nodeActionViewControllerDelegate?.nodeAction(nodeAction, didSelect: action, for: node, from: sender) - case .fileLink: - fileLinkActionViewControllerDelegate?.nodeAction(nodeAction, didSelect: action, for: node, from: sender) - case .unknown: - break - } + presenter.isKind(of: NodeVersionsViewController.self) } } diff --git a/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewRouterNodeActionAdapter.swift b/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewRouterNodeActionAdapter.swift new file mode 100644 index 0000000000..4a8cd55d57 --- /dev/null +++ b/iMEGA/AudioPlayer/Scenes/AudioPlayerScene/AudioPlayerViewRouterNodeActionAdapter.swift @@ -0,0 +1,23 @@ +final class AudioPlayerViewRouterNodeActionAdapter: NodeActionViewControllerDelegate { + + private let configEntity: AudioPlayerConfigEntity + private(set) var nodeActionViewControllerDelegate: NodeActionViewControllerGenericDelegate? + private(set) var fileLinkActionViewControllerDelegate: FileLinkActionViewControllerDelegate? + + init(configEntity: AudioPlayerConfigEntity, nodeActionViewControllerDelegate: NodeActionViewControllerGenericDelegate?, fileLinkActionViewControllerDelegate: FileLinkActionViewControllerDelegate?) { + self.configEntity = configEntity + self.nodeActionViewControllerDelegate = nodeActionViewControllerDelegate + self.fileLinkActionViewControllerDelegate = fileLinkActionViewControllerDelegate + } + + func nodeAction(_ nodeAction: NodeActionViewController, didSelect action: MegaNodeActionType, for node: MEGANode, from sender: Any) { + switch configEntity.nodeOriginType { + case .folderLink, .chat: + nodeActionViewControllerDelegate?.nodeAction(nodeAction, didSelect: action, for: node, from: sender) + case .fileLink: + fileLinkActionViewControllerDelegate?.nodeAction(nodeAction, didSelect: action, for: node, from: sender) + case .unknown: + break + } + } +} diff --git a/iMEGA/AudioPlayer/Scenes/AudioPlaylistScene/AudioPlaylistIndexedDelegate.swift b/iMEGA/AudioPlayer/Scenes/AudioPlaylistScene/AudioPlaylistIndexedDelegate.swift index 425a3a3afc..b64e0d7fe7 100644 --- a/iMEGA/AudioPlayer/Scenes/AudioPlaylistScene/AudioPlaylistIndexedDelegate.swift +++ b/iMEGA/AudioPlayer/Scenes/AudioPlaylistScene/AudioPlaylistIndexedDelegate.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n protocol AudioPlaylistDelegate: AnyObject { func didSelect(item: AudioPlayerItem) diff --git a/iMEGA/AudioPlayer/Scenes/AudioPlaylistScene/AudioPlaylistViewController.swift b/iMEGA/AudioPlayer/Scenes/AudioPlaylistScene/AudioPlaylistViewController.swift index 7e39468f54..b9ec83f4ef 100644 --- a/iMEGA/AudioPlayer/Scenes/AudioPlaylistScene/AudioPlaylistViewController.swift +++ b/iMEGA/AudioPlayer/Scenes/AudioPlaylistScene/AudioPlaylistViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit final class AudioPlaylistViewController: UIViewController { diff --git a/iMEGA/AudioPlayer/UseCases/Domain/AudioPlayerConfigEntity.swift b/iMEGA/AudioPlayer/UseCases/Domain/AudioPlayerConfigEntity.swift index 77a1e1e5fc..c539685e63 100644 --- a/iMEGA/AudioPlayer/UseCases/Domain/AudioPlayerConfigEntity.swift +++ b/iMEGA/AudioPlayer/UseCases/Domain/AudioPlayerConfigEntity.swift @@ -39,7 +39,7 @@ struct AudioPlayerConfigEntity { return .default } - enum NodeOriginType { + enum NodeOriginType: CaseIterable { case folderLink case fileLink case chat diff --git a/iMEGA/CallScene/EncourageGuestUserToJoinMegaScene/EncourageGuestUserToJoinMegaViewController.swift b/iMEGA/CallScene/EncourageGuestUserToJoinMegaScene/EncourageGuestUserToJoinMegaViewController.swift index 2dfe1feb50..f4884c31a3 100644 --- a/iMEGA/CallScene/EncourageGuestUserToJoinMegaScene/EncourageGuestUserToJoinMegaViewController.swift +++ b/iMEGA/CallScene/EncourageGuestUserToJoinMegaScene/EncourageGuestUserToJoinMegaViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit class EncourageGuestUserToJoinMegaViewController: UIViewController { diff --git a/iMEGA/CallScene/EndMeetingOptions/EndMeetingOptionsRouter.swift b/iMEGA/CallScene/EndMeetingOptions/EndMeetingOptionsRouter.swift index 2eec6e40f5..5498816698 100644 --- a/iMEGA/CallScene/EndMeetingOptions/EndMeetingOptionsRouter.swift +++ b/iMEGA/CallScene/EndMeetingOptions/EndMeetingOptionsRouter.swift @@ -26,7 +26,7 @@ final class EndMeetingOptionsRouter: EndMeetingOptionsRouting { } func start() { - guard let viewController = build() as? PanModalPresentable & UIViewController else { return } + guard let viewController = build() as? any PanModalPresentable & UIViewController else { return } if let sender = sender, UIDevice.current.iPad { viewController.modalPresentationStyle = .popover viewController.popoverPresentationController?.sourceView = sender diff --git a/iMEGA/CallScene/EndMeetingOptions/EndMeetingOptionsViewViewController.swift b/iMEGA/CallScene/EndMeetingOptions/EndMeetingOptionsViewViewController.swift index 086c3182f9..70d753b5e2 100644 --- a/iMEGA/CallScene/EndMeetingOptions/EndMeetingOptionsViewViewController.swift +++ b/iMEGA/CallScene/EndMeetingOptions/EndMeetingOptionsViewViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import PanModal import UIKit diff --git a/iMEGA/CallScene/EnterMeetingLinkScene/EnterMeetingLinkControllerWrapper.swift b/iMEGA/CallScene/EnterMeetingLinkScene/EnterMeetingLinkControllerWrapper.swift index 9ea719dc15..f2bcf824a2 100644 --- a/iMEGA/CallScene/EnterMeetingLinkScene/EnterMeetingLinkControllerWrapper.swift +++ b/iMEGA/CallScene/EnterMeetingLinkScene/EnterMeetingLinkControllerWrapper.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit final class EnterMeetingLinkControllerWrapper: NSObject { diff --git a/iMEGA/CallScene/EnterMeetingLinkScene/EnterMeetingLinkRouter.swift b/iMEGA/CallScene/EnterMeetingLinkScene/EnterMeetingLinkRouter.swift index ea443376e9..c539d531eb 100644 --- a/iMEGA/CallScene/EnterMeetingLinkScene/EnterMeetingLinkRouter.swift +++ b/iMEGA/CallScene/EnterMeetingLinkScene/EnterMeetingLinkRouter.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n import MEGAPresentation protocol EnterMeetingLinkRouting: Routing { diff --git a/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelRouter.swift b/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelRouter.swift index 732e87d7d6..23c54f2b81 100644 --- a/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelRouter.swift +++ b/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelRouter.swift @@ -70,18 +70,18 @@ final class MeetingFloatingPanelRouter: MeetingFloatingPanelRouting { chatRoom: chatRoom, isSpeakerEnabled: isSpeakerEnabled, callCoordinatorUseCase: CallCoordinatorUseCase(), - callUseCase: CallUseCase(repository: CallRepository(chatSdk: MEGASdkManager.sharedMEGAChatSdk(), callActionManager: CallActionManager.shared)), + callUseCase: CallUseCase(repository: CallRepository.newRepo), audioSessionUseCase: AudioSessionUseCase(audioSessionRepository: audioSessionRepository), permissionHandler: DevicePermissionsHandler.makeHandler(), captureDeviceUseCase: CaptureDeviceUseCase(repo: CaptureDeviceRepository()), - localVideoUseCase: CallLocalVideoUseCase(repository: CallLocalVideoRepository(chatSdk: MEGASdkManager.sharedMEGAChatSdk())), + localVideoUseCase: CallLocalVideoUseCase(repository: CallLocalVideoRepository(chatSdk: .shared)), accountUseCase: AccountUseCase(repository: AccountRepository.newRepo), chatRoomUseCase: chatRoomUseCase, megaHandleUseCase: megaHandleUseCase ) let userImageUseCase = UserImageUseCase( - userImageRepo: UserImageRepository(sdk: MEGASdkManager.sharedMEGASdk()), + userImageRepo: UserImageRepository.newRepo, userStoreRepo: UserStoreRepository(store: MEGAStore.shareInstance()), thumbnailRepo: ThumbnailRepository.newRepo, fileSystemRepo: FileSystemRepository.newRepo @@ -99,7 +99,7 @@ final class MeetingFloatingPanelRouter: MeetingFloatingPanelRouting { } func start() { - guard let viewController = build() as? PanModalPresentable & UIViewController else { return } + guard let viewController = build() as? any PanModalPresentable & UIViewController else { return } viewController.modalPresentationStyle = .custom viewController.modalPresentationCapturesStatusBarAppearance = true viewController.transitioningDelegate = PanModalPresentationDelegate.default diff --git a/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelViewController.swift b/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelViewController.swift index 8d95b30299..79dc5006ac 100644 --- a/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelViewController.swift +++ b/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelViewController.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import PanModal import UIKit diff --git a/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelViewModel.swift b/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelViewModel.swift index 5e9b802a3c..c9e9be8e30 100644 --- a/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelViewModel.swift +++ b/iMEGA/CallScene/FloatingPanel/MeetingFloatingPanelViewModel.swift @@ -308,7 +308,7 @@ final class MeetingFloatingPanelViewModel: ViewModelType { } private func currentCameraPosition() -> CameraPositionEntity { - return captureDeviceUseCase.wideAngleCameraLocalizedName(postion: .front) == localVideoUseCase.videoDeviceSelected() ? .front : .back + return captureDeviceUseCase.wideAngleCameraLocalizedName(position: .front) == localVideoUseCase.videoDeviceSelected() ? .front : .back } private func sessionRouteChanged(routeChangedReason: AudioSessionRouteChangedReason) { @@ -330,7 +330,7 @@ final class MeetingFloatingPanelViewModel: ViewModelType { } private func switchCamera(backCameraOn: Bool) { - guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(postion: backCameraOn ? .back : .front), + guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(position: backCameraOn ? .back : .front), localVideoUseCase.videoDeviceSelected() != selectCameraLocalizedString else { return } @@ -342,7 +342,7 @@ final class MeetingFloatingPanelViewModel: ViewModelType { } private func isBackCameraSelected() -> Bool { - guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(postion: .back), + guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(position: .back), localVideoUseCase.videoDeviceSelected() == selectCameraLocalizedString else { return false } @@ -511,4 +511,12 @@ extension MeetingFloatingPanelViewModel: CallCallbacksUseCaseProtocol { func localAvFlagsUpdated(video: Bool, audio: Bool) { invokeCommand?(.microphoneMuted(muted: !audio)) } + + func waitingRoomUsersEntered(with handles: [HandleEntity]) { + // Next ticket: Present the waiting room users in meeting floating panel + } + + func waitingRoomUsersLeave(with handles: [HandleEntity]) { + // Next ticket: Present the waiting room users in meeting floating panel + } } diff --git a/iMEGA/CallScene/FloatingPanel/ParticpantCell/MeetingParticipantTableViewCell.swift b/iMEGA/CallScene/FloatingPanel/ParticpantCell/MeetingParticipantTableViewCell.swift index 48d0809549..fc92217ea6 100644 --- a/iMEGA/CallScene/FloatingPanel/ParticpantCell/MeetingParticipantTableViewCell.swift +++ b/iMEGA/CallScene/FloatingPanel/ParticpantCell/MeetingParticipantTableViewCell.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGAPresentation import UIKit @@ -14,7 +15,7 @@ class MeetingParticipantTableViewCell: UITableViewCell, ViewType { super.awakeFromNib() moderatorTextLabel.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.1957759226) moderatorTextLabel.layer.cornerRadius = 4.0 - moderatorTextLabel.text = " \(NSLocalizedString("meetings.participant.moderator", comment: "")) " + moderatorTextLabel.text = " \(Strings.Localizable.Meetings.Participant.moderator) " } var viewModel: MeetingParticipantViewModel? { diff --git a/iMEGA/CallScene/FloatingPanel/ParticpantCell/MeetingParticipantViewModel.swift b/iMEGA/CallScene/FloatingPanel/ParticpantCell/MeetingParticipantViewModel.swift index e9e597e1d7..66fbbf2e1d 100644 --- a/iMEGA/CallScene/FloatingPanel/ParticpantCell/MeetingParticipantViewModel.swift +++ b/iMEGA/CallScene/FloatingPanel/ParticpantCell/MeetingParticipantViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import MEGAPresentation enum MeetingParticipantViewAction: ActionType { diff --git a/iMEGA/CallScene/FloatingPanel/ParticpiantInfo/MeetingParticpiantInfoViewModel.swift b/iMEGA/CallScene/FloatingPanel/ParticpiantInfo/MeetingParticpiantInfoViewModel.swift index 5c2a7a6c7a..a469e5300b 100644 --- a/iMEGA/CallScene/FloatingPanel/ParticpiantInfo/MeetingParticpiantInfoViewModel.swift +++ b/iMEGA/CallScene/FloatingPanel/ParticpiantInfo/MeetingParticpiantInfoViewModel.swift @@ -1,11 +1,11 @@ import MEGADomain +import MEGAL10n import MEGAPresentation enum MeetingParticpiantInfoAction: ActionType { case onViewReady case showInfo case sendMessage - case addToContact case makeModerator case removeModerator case removeParticipant @@ -23,7 +23,6 @@ struct MeetingParticpiantInfoViewModel: ViewModelType { private let userImageUseCase: any UserImageUseCaseProtocol private let chatRoomUseCase: any ChatRoomUseCaseProtocol private let chatRoomUserUseCase: any ChatRoomUserUseCaseProtocol - private let userInviteUseCase: any UserInviteUseCaseProtocol private let megaHandleUseCase: any MEGAHandleUseCaseProtocol private let isMyselfModerator: Bool private let router: any MeetingParticpiantInfoViewRouting @@ -32,7 +31,6 @@ struct MeetingParticpiantInfoViewModel: ViewModelType { userImageUseCase: some UserImageUseCaseProtocol, chatRoomUseCase: some ChatRoomUseCaseProtocol, chatRoomUserUseCase: some ChatRoomUserUseCaseProtocol, - userInviteUseCase: some UserInviteUseCaseProtocol, megaHandleUseCase: some MEGAHandleUseCaseProtocol, isMyselfModerator: Bool, router: some MeetingParticpiantInfoViewRouting) { @@ -40,7 +38,6 @@ struct MeetingParticpiantInfoViewModel: ViewModelType { self.userImageUseCase = userImageUseCase self.chatRoomUseCase = chatRoomUseCase self.chatRoomUserUseCase = chatRoomUserUseCase - self.userInviteUseCase = userInviteUseCase self.megaHandleUseCase = megaHandleUseCase self.isMyselfModerator = isMyselfModerator self.router = router @@ -57,8 +54,6 @@ struct MeetingParticpiantInfoViewModel: ViewModelType { router.showInfo() case .sendMessage: sendMessage() - case .addToContact: - addToContact() case .makeModerator: router.makeParticipantAsModerator() case .removeModerator: @@ -141,35 +136,6 @@ struct MeetingParticpiantInfoViewModel: ViewModelType { } } } - - private func addToContact() { - guard let email = participant.email else { return } - - userInviteUseCase.sendInvite(forEmail: email) { result in - switch result { - case .success: - self.router.showInviteSuccess(email: email) - case .failure(let error): - errorInvitingToContact(error, email: email) - } - } - } - - private func errorInvitingToContact(_ error: InviteErrorEntity, email: String) { - var errorString = "" - switch error { - case .generic(let error): - errorString = error - case .ownEmailEntered: - errorString = Strings.Localizable.noNeedToAddYourOwnEmailAddress - case .alreadyAContact: - errorString = Strings.Localizable.alreadyAContact(email) - case .isInOutgoingContactRequest: - errorString = Strings.Localizable.Dialog.InviteContact.outgoingContactRequest - errorString = errorString.replacingOccurrences(of: "[X]", with: email) - } - self.router.showInviteErrorMessage(errorString) - } private func infoAction() -> ActionSheetAction { ActionSheetAction(title: Strings.Localizable.info, @@ -207,15 +173,6 @@ struct MeetingParticpiantInfoViewModel: ViewModelType { } } - private func addContactAction() -> ActionSheetAction { - ActionSheetAction(title: Strings.Localizable.addContact, - detail: nil, - image: Asset.Images.Meetings.addContactMeetings.image, - style: .default) { - dispatch(.addToContact) - } - } - private func removeContactAction() -> ActionSheetAction { ActionSheetAction(title: Strings.Localizable.removeParticipant, detail: nil, diff --git a/iMEGA/CallScene/FloatingPanel/ParticpiantInfo/MeetingParticpiantInfoViewRouter.swift b/iMEGA/CallScene/FloatingPanel/ParticpiantInfo/MeetingParticpiantInfoViewRouter.swift index 038bd5d662..9503ae2230 100644 --- a/iMEGA/CallScene/FloatingPanel/ParticpiantInfo/MeetingParticpiantInfoViewRouter.swift +++ b/iMEGA/CallScene/FloatingPanel/ParticpiantInfo/MeetingParticpiantInfoViewRouter.swift @@ -1,5 +1,6 @@ import ChatRepo import MEGADomain +import MEGAL10n import MEGAPresentation import MEGARepo import MEGASDKRepo @@ -7,8 +8,6 @@ import MEGASDKRepo protocol MeetingParticpiantInfoViewRouting: Routing { func showInfo() func openChatRoom(withChatId chatId: UInt64) - func showInviteSuccess(email: String) - func showInviteErrorMessage(_ message: String) func makeParticipantAsModerator() func removeModeratorPrivilage() func removeParticipant() @@ -36,7 +35,7 @@ struct MeetingParticpiantInfoViewRouter: MeetingParticpiantInfoViewRouting { func build() -> UIViewController { let userImageUseCase = UserImageUseCase( - userImageRepo: UserImageRepository(sdk: MEGASdkManager.sharedMEGASdk()), + userImageRepo: UserImageRepository.newRepo, userStoreRepo: UserStoreRepository(store: MEGAStore.shareInstance()), thumbnailRepo: ThumbnailRepository.newRepo, fileSystemRepo: FileSystemRepository.newRepo @@ -45,8 +44,6 @@ struct MeetingParticpiantInfoViewRouter: MeetingParticpiantInfoViewRouting { let chatRoomUseCase = ChatRoomUseCase(chatRoomRepo: ChatRoomRepository.sharedRepo) let chatRoomUserUseCase = ChatRoomUserUseCase(chatRoomRepo: ChatRoomUserRepository.newRepo, userStoreRepo: UserStoreRepository(store: MEGAStore.shareInstance())) - - let userInviteUseCase = UserInviteUseCase(repo: UserInviteRepository(sdk: MEGASdkManager.sharedMEGASdk())) let megaHandleUseCase = MEGAHandleUseCase(repo: MEGAHandleRepository.newRepo) @@ -54,7 +51,6 @@ struct MeetingParticpiantInfoViewRouter: MeetingParticpiantInfoViewRouting { userImageUseCase: userImageUseCase, chatRoomUseCase: chatRoomUseCase, chatRoomUserUseCase: chatRoomUserUseCase, - userInviteUseCase: userInviteUseCase, megaHandleUseCase: megaHandleUseCase, isMyselfModerator: isMyselfModerator, router: self) diff --git a/iMEGA/CallScene/FloatingPanel/QuickAction/CircularView.swift b/iMEGA/CallScene/FloatingPanel/QuickAction/CircularView.swift index f44e65f38d..80848ca129 100644 --- a/iMEGA/CallScene/FloatingPanel/QuickAction/CircularView.swift +++ b/iMEGA/CallScene/FloatingPanel/QuickAction/CircularView.swift @@ -1,4 +1,3 @@ - import UIKit final class CircularView: UIView { diff --git a/iMEGA/CallScene/FloatingPanel/QuickAction/MeetingQuickActionView.swift b/iMEGA/CallScene/FloatingPanel/QuickAction/MeetingQuickActionView.swift index c4d7932820..1dc3af8495 100644 --- a/iMEGA/CallScene/FloatingPanel/QuickAction/MeetingQuickActionView.swift +++ b/iMEGA/CallScene/FloatingPanel/QuickAction/MeetingQuickActionView.swift @@ -1,4 +1,3 @@ - import MediaPlayer import MEGADomain import UIKit diff --git a/iMEGA/CallScene/FloatingPanel/Utils/ParticipantsAddingViewFactory.swift b/iMEGA/CallScene/FloatingPanel/Utils/ParticipantsAddingViewFactory.swift index 5fda447871..4326f50484 100644 --- a/iMEGA/CallScene/FloatingPanel/Utils/ParticipantsAddingViewFactory.swift +++ b/iMEGA/CallScene/FloatingPanel/Utils/ParticipantsAddingViewFactory.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n struct ParticipantsAddingViewFactory { let accountUseCase: any AccountUseCaseProtocol diff --git a/iMEGA/CallScene/HangOrEndCallScene/HangOrEndCallView.swift b/iMEGA/CallScene/HangOrEndCallScene/HangOrEndCallView.swift index 1767e3fb90..07b30e447a 100644 --- a/iMEGA/CallScene/HangOrEndCallScene/HangOrEndCallView.swift +++ b/iMEGA/CallScene/HangOrEndCallScene/HangOrEndCallView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import MEGASwiftUI import SwiftUI diff --git a/iMEGA/CallScene/MeetingContainerRouter.swift b/iMEGA/CallScene/MeetingContainerRouter.swift index 4023568157..72bbc24b4c 100644 --- a/iMEGA/CallScene/MeetingContainerRouter.swift +++ b/iMEGA/CallScene/MeetingContainerRouter.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import MEGAPermissions import MEGAPresentation import MEGASDKRepo @@ -40,7 +41,7 @@ final class MeetingContainerRouter: MeetingContainerRouting { private lazy var chatRoomUseCase = ChatRoomUseCase(chatRoomRepo: ChatRoomRepository.sharedRepo) private var createCallUseCase: CallUseCase { - let callRepository = CallRepository(chatSdk: MEGASdkManager.sharedMEGAChatSdk(), callActionManager: CallActionManager.shared) + let callRepository = CallRepository(chatSdk: MEGAChatSdk.shared, callActionManager: CallActionManager.shared) return CallUseCase(repository: callRepository) } @@ -66,7 +67,7 @@ final class MeetingContainerRouter: MeetingContainerRouting { func build() -> UIViewController { let meetingNoUserJoinedUseCase = MeetingNoUserJoinedUseCase(repository: MeetingNoUserJoinedRepository.sharedRepo) - let analyticsEventUseCase = AnalyticsEventUseCase(repository: AnalyticsRepository(sdk: MEGASdkManager.sharedMEGASdk())) + let analyticsEventUseCase = AnalyticsEventUseCase(repository: AnalyticsRepository(sdk: MEGASdk.shared)) let megaHandleUseCase = MEGAHandleUseCase(repo: MEGAHandleRepository.newRepo) let viewModel = MeetingContainerViewModel(router: self, chatRoom: chatRoom, diff --git a/iMEGA/CallScene/MeetingContainerViewController.swift b/iMEGA/CallScene/MeetingContainerViewController.swift index 49c449aef8..e0d64e2853 100644 --- a/iMEGA/CallScene/MeetingContainerViewController.swift +++ b/iMEGA/CallScene/MeetingContainerViewController.swift @@ -1,4 +1,3 @@ - final class MeetingContainerViewController: UINavigationController { private let viewModel: MeetingContainerViewModel diff --git a/iMEGA/CallScene/ParticipantsLayout/MeetingParticipantsLayoutViewController.swift b/iMEGA/CallScene/ParticipantsLayout/MeetingParticipantsLayoutViewController.swift index 1ad6fe11dc..0d596549b8 100644 --- a/iMEGA/CallScene/ParticipantsLayout/MeetingParticipantsLayoutViewController.swift +++ b/iMEGA/CallScene/ParticipantsLayout/MeetingParticipantsLayoutViewController.swift @@ -1,6 +1,7 @@ import Combine import Foundation import MEGADomain +import MEGAL10n import MEGAPresentation final class MeetingParticipantsLayoutViewController: UIViewController, ViewType { diff --git a/iMEGA/CallScene/ParticipantsLayout/MeetingParticipantsLayoutViewModel.swift b/iMEGA/CallScene/ParticipantsLayout/MeetingParticipantsLayoutViewModel.swift index d796e5fb2c..21a4bd45f3 100644 --- a/iMEGA/CallScene/ParticipantsLayout/MeetingParticipantsLayoutViewModel.swift +++ b/iMEGA/CallScene/ParticipantsLayout/MeetingParticipantsLayoutViewModel.swift @@ -1,6 +1,7 @@ import Combine import Foundation import MEGADomain +import MEGAL10n import MEGAPresentation enum CallViewAction: ActionType { @@ -315,7 +316,7 @@ final class MeetingParticipantsLayoutViewModel: NSObject, ViewModelType { } private func isBackCameraSelected() -> Bool { - guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(postion: .back), + guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(position: .back), localVideoUseCase.videoDeviceSelected() == selectCameraLocalizedString else { return false } @@ -800,6 +801,14 @@ extension MeetingParticipantsLayoutViewModel: CallCallbacksUseCaseProtocol { } } + func waitingRoomUsersEntered(with handles: [HandleEntity]) { + // Next ticket: Logic for waitingRoomUsersEntered + } + + func waitingRoomUsersLeave(with handles: [HandleEntity]) { + // Next ticket: Logic for waitingRoomUsersLeave + } + func updateParticipant(_ participant: CallParticipantEntity) { guard let participantUpdated = callParticipants.first(where: {$0 == participant}) else { MEGALogError("Error getting participant updated") diff --git a/iMEGA/CallScene/ParticipantsLayout/OptionsMenu/MeetingOptionsMenuViewModel.swift b/iMEGA/CallScene/ParticipantsLayout/OptionsMenu/MeetingOptionsMenuViewModel.swift index d1e58d8161..b72fb63c4d 100644 --- a/iMEGA/CallScene/ParticipantsLayout/OptionsMenu/MeetingOptionsMenuViewModel.swift +++ b/iMEGA/CallScene/ParticipantsLayout/OptionsMenu/MeetingOptionsMenuViewModel.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation enum MeetingOptionsMenuAction: ActionType { diff --git a/iMEGA/CallScene/ParticipantsLayout/Utils/TonePlayer.swift b/iMEGA/CallScene/ParticipantsLayout/Utils/TonePlayer.swift index c176a13ec7..954bbfe098 100644 --- a/iMEGA/CallScene/ParticipantsLayout/Utils/TonePlayer.swift +++ b/iMEGA/CallScene/ParticipantsLayout/Utils/TonePlayer.swift @@ -1,4 +1,3 @@ - import Foundation import MEGADomain diff --git a/iMEGA/CallScene/ParticipantsLayout/Views/EmptyMeetingMessage/EmptyMeetingMessageView.swift b/iMEGA/CallScene/ParticipantsLayout/Views/EmptyMeetingMessage/EmptyMeetingMessageView.swift index 3b7cf091f4..d410f8df36 100644 --- a/iMEGA/CallScene/ParticipantsLayout/Views/EmptyMeetingMessage/EmptyMeetingMessageView.swift +++ b/iMEGA/CallScene/ParticipantsLayout/Views/EmptyMeetingMessage/EmptyMeetingMessageView.swift @@ -1,4 +1,3 @@ - import Foundation class EmptyMeetingMessageView: UIView { diff --git a/iMEGA/CallScene/ParticipantsLayout/Views/Notifications/CallNotificationView.swift b/iMEGA/CallScene/ParticipantsLayout/Views/Notifications/CallNotificationView.swift index e788431c7a..c8bfc50102 100644 --- a/iMEGA/CallScene/ParticipantsLayout/Views/Notifications/CallNotificationView.swift +++ b/iMEGA/CallScene/ParticipantsLayout/Views/Notifications/CallNotificationView.swift @@ -1,4 +1,3 @@ - import Foundation class CallNotificationView: UIView { diff --git a/iMEGA/CallScene/ParticipantsLayout/Views/TitleView/CallTitleView.swift b/iMEGA/CallScene/ParticipantsLayout/Views/TitleView/CallTitleView.swift index 49ada9b6a0..82f109754a 100644 --- a/iMEGA/CallScene/ParticipantsLayout/Views/TitleView/CallTitleView.swift +++ b/iMEGA/CallScene/ParticipantsLayout/Views/TitleView/CallTitleView.swift @@ -1,4 +1,3 @@ - import Foundation class CallTitleView: UIView { diff --git a/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewController.swift b/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewController.swift index 102e37d351..6ec1e2bcf1 100644 --- a/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewController.swift +++ b/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewController.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAUIKit import UIKit diff --git a/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewModel.swift b/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewModel.swift index d5e5948c79..8030b9d360 100644 --- a/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewModel.swift +++ b/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import MEGAPermissions import MEGAPresentation @@ -368,7 +369,7 @@ final class MeetingCreatingViewModel: ViewModelType { private func selectFrontCameraIfNeeded() { if isBackCameraSelected() { - guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(postion: .front) else { + guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(position: .front) else { return } localVideoUseCase.selectCamera(withLocalizedName: selectCameraLocalizedString) { _ in } @@ -376,7 +377,7 @@ final class MeetingCreatingViewModel: ViewModelType { } private func isBackCameraSelected() -> Bool { - guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(postion: .back), + guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(position: .back), localVideoUseCase.videoDeviceSelected() == selectCameraLocalizedString else { return false } @@ -385,7 +386,7 @@ final class MeetingCreatingViewModel: ViewModelType { } private func switchCamera() { - guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(postion: isBackCameraSelected() ? .front : .back), + guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(position: isBackCameraSelected() ? .front : .back), localVideoUseCase.videoDeviceSelected() != selectCameraLocalizedString else { return } diff --git a/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewRouter.swift b/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewRouter.swift index 1e367f8b88..bc6c3f8675 100644 --- a/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewRouter.swift +++ b/iMEGA/CallScene/StartMeetingScene/MeetingCreatingViewRouter.swift @@ -31,7 +31,7 @@ class MeetingCreatingViewRouter: NSObject, MeetingCreatingViewRouting { @objc func build() -> UIViewController { let audioSessionRepository = AudioSessionRepository(audioSession: AVAudioSession.sharedInstance(), callActionManager: CallActionManager.shared) let userImageUseCase = UserImageUseCase( - userImageRepo: UserImageRepository(sdk: MEGASdkManager.sharedMEGASdk()), + userImageRepo: UserImageRepository(sdk: .shared), userStoreRepo: UserStoreRepository(store: MEGAStore.shareInstance()), thumbnailRepo: ThumbnailRepository.newRepo, fileSystemRepo: FileSystemRepository.newRepo @@ -40,9 +40,9 @@ class MeetingCreatingViewRouter: NSObject, MeetingCreatingViewRouting { let vm = MeetingCreatingViewModel( router: self, type: type, - meetingUseCase: MeetingCreatingUseCase(repository: MeetingCreatingRepository(chatSdk: MEGASdkManager.sharedMEGAChatSdk(), sdk: MEGASdkManager.sharedMEGASdk(), callActionManager: CallActionManager.shared)), + meetingUseCase: MeetingCreatingUseCase(repository: MeetingCreatingRepository(chatSdk: .shared, sdk: .shared, callActionManager: .shared)), audioSessionUseCase: AudioSessionUseCase(audioSessionRepository: audioSessionRepository), - localVideoUseCase: CallLocalVideoUseCase(repository: CallLocalVideoRepository(chatSdk: MEGASdkManager.sharedMEGAChatSdk())), + localVideoUseCase: CallLocalVideoUseCase(repository: CallLocalVideoRepository(chatSdk: .shared)), captureDeviceUseCase: CaptureDeviceUseCase(repo: CaptureDeviceRepository()), permissionHandler: DevicePermissionsHandler.makeHandler(), userImageUseCase: userImageUseCase, diff --git a/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomJoinPanelView.swift b/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomJoinPanelView.swift index c7e3cd0b15..0617f88b98 100644 --- a/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomJoinPanelView.swift +++ b/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomJoinPanelView.swift @@ -1,26 +1,36 @@ +import MEGAL10n +import MEGASwiftUI import SwiftUI struct WaitingRoomJoinPanelView: View { - let tapJoinAction: () -> Void + let tapJoinAction: (String, String) -> Void + let appearFocused: Bool @State private var firstName: String = "" @State private var lastName: String = "" + private var disableJoinButton: Bool { + firstName.isEmpty || lastName.isEmpty + } + var body: some View { VStack { HStack { - TextField(Strings.Localizable.Meetings.WaitingRoom.Guest.firstName, text: $firstName) - .font(.body) - .frame(width: 85) - Spacer() - .frame(width: 20) - TextField(Strings.Localizable.Meetings.WaitingRoom.Guest.lastName, text: $lastName) - .font(.body) - .frame(width: 85) + FocusableNameTextFieldView( + placeholder: Strings.Localizable.Meetings.WaitingRoom.Guest.firstName, + text: $firstName, + appearFocused: appearFocused + ) + .frame(maxWidth: 120) + + FocusableNameTextFieldView( + placeholder: Strings.Localizable.Meetings.WaitingRoom.Guest.lastName, + text: $lastName + ) + .frame(maxWidth: 120) } - .padding() - Button { - tapJoinAction() + tapJoinAction(firstName, lastName) + hideKeyboard() } label: { Text(Strings.Localizable.Meetings.WaitingRoom.Guest.join) .foregroundColor(.white) @@ -29,17 +39,42 @@ struct WaitingRoomJoinPanelView: View { .background(Color(Colors.General.Green._00C29A.name)) } .cornerRadius(8) - .disabled(firstName.isEmpty || lastName.isEmpty) + .disabled(disableJoinButton) + .opacity(disableJoinButton ? 0.3 : 1) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(Colors.General.Black._1c1c1e.name)) } + + struct FocusableNameTextFieldView: View { + let placeholder: String + @Binding var text: String + var appearFocused: Bool = false + + var body: some View { + FocusableTextFieldView( + placeholder: placeholder, + text: $text, + appearFocused: appearFocused + ) + .font(.body) + .padding() + } + } } struct WaitingRoomJoinPanelView_Previews: PreviewProvider { static var previews: some View { - WaitingRoomJoinPanelView(tapJoinAction: {}) - .preferredColorScheme(.dark) - .previewLayout(.sizeThatFits) + VStack { + Spacer() + ZStack { + WaitingRoomJoinPanelView(tapJoinAction: {firstName, lastName in + MEGALogDebug("firstName: \(firstName), lastName: \(lastName)") + }, appearFocused: false) + } + .frame(height: 142) + } + .preferredColorScheme(.dark) + .background(Color.black) } } diff --git a/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomUserAvatarView.swift b/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomUserAvatarView.swift index d331888d57..f8765822e4 100644 --- a/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomUserAvatarView.swift +++ b/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomUserAvatarView.swift @@ -1,11 +1,12 @@ import SwiftUI struct WaitingRoomUserAvatarView: View { - - @Binding var avatar: Image + let avatar: Image var body: some View { avatar + .resizable() + .scaledToFill() .frame(width: 80, height: 80) .clipShape(Circle()) .padding(2) @@ -18,7 +19,7 @@ struct WaitingRoomUserAvatarView: View { struct WaitingRoomUserAvatarView_Previews: PreviewProvider { static var previews: some View { - WaitingRoomUserAvatarView(avatar: .constant(Image(Color.red, CGSize(width: 100, height: 100)))) + WaitingRoomUserAvatarView(avatar: Image(Color.red, CGSize(width: 100, height: 100))) .padding(20) .background(Color.black) .previewLayout(.sizeThatFits) diff --git a/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomView.swift b/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomView.swift index 28103a4654..8f7eb167c3 100644 --- a/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomView.swift +++ b/iMEGA/CallScene/WaitingRoom/Views/WaitingRoomView.swift @@ -1,17 +1,31 @@ +import MEGAL10n +import MEGASwiftUI import SwiftUI struct WaitingRoomView: View { @ObservedObject var viewModel: WaitingRoomViewModel + private enum UI { + static let contentBottomPadding: CGFloat = 140 + } + var body: some View { - ZStack { - WaitingRoomUserAvatarView(avatar: .constant(Image(Color.red, CGSize(width: 100, height: 100)))) + GeometryReader { proxy in + ZStack { + waitingRoomContentView() + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .ignoresSafeArea() + .onAppear { + viewModel.screenSize = proxy.size + } + .onChange(of: proxy.size) { newSize in + viewModel.screenSize = newSize + } } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .ignoresSafeArea() + .ignoresSafeArea(.keyboard) .overlay( waitingRoomMessageView() - .opacity(viewModel.viewState == .waitForHostToLetIn ? 1 : 0) , alignment: .top ) .overlay( @@ -19,17 +33,48 @@ struct WaitingRoomView: View { , alignment: .bottom ) } + + @ViewBuilder + func waitingRoomContentView() -> some View { + if viewModel.isVideoEnabled, let videoImage = viewModel.videoImage { + GeometryReader { proxy in + let bottomPadding = viewModel.isLandscape ? 0.0 : UI.contentBottomPadding + let videoSize = viewModel.calculateVideoSize() + Image(uiImage: videoImage) + .resizable() + .scaledToFill() + .frame(width: videoSize.width, height: videoSize.height) + .cornerRadius(16) + .position(x: proxy.size.width / 2, y: (proxy.size.height - bottomPadding) / 2 ) + } + } + + if let userAvatar = viewModel.userAvatar { + WaitingRoomUserAvatarView(avatar: Image(uiImage: userAvatar)) + .padding(.bottom, UI.contentBottomPadding) + .opacity(viewModel.isVideoEnabled ? 0 : 1) + } + } func waitingRoomMessageView() -> some View { - WaitingRoomMessageView(title: Strings.Localizable.Meetings.WaitingRoom.Message.waitForHostToLetYouIn) + WaitingRoomMessageView(title: viewModel.waitingRoomMessage) .padding(26) + .opacity(viewModel.showWaitingRoomMessage ? 1 : 0) } func waitingRoomBottomView() -> some View { VStack(spacing: 0) { - WaitingRoomControlsView(isVideoEnabled: $viewModel.isVideoEnabled, - isMicrophoneEnabled: $viewModel.isMicrophoneEnabled, - isSpeakerEnabled: $viewModel.isSpeakerEnabled) + WaitingRoomControlsView( + isVideoEnabled: $viewModel.isVideoEnabled.onChange { enable in + viewModel.enableLocalVideo(enabled: enable) + }, + isMicrophoneEnabled: $viewModel.isMicrophoneEnabled.onChange { enable in + viewModel.enableLocalMicrophone(enabled: enable) + }, + isSpeakerEnabled: $viewModel.isSpeakerEnabled.onChange { enable in + viewModel.enableLoudSpeaker(enabled: enable) + } + ) ZStack { Spacer() .opacity(viewModel.viewState == .waitForHostToLetIn ? 1 : 0) @@ -37,10 +82,12 @@ struct WaitingRoomView: View { ProgressView() .opacity(viewModel.viewState == .guestJoining ? 1 : 0) - WaitingRoomJoinPanelView(tapJoinAction: viewModel.tapJoinAction) + WaitingRoomJoinPanelView( + tapJoinAction: viewModel.tapJoinAction, + appearFocused: viewModel.viewState == .guestJoin) .opacity(viewModel.viewState == .guestJoin ? 1 : 0) } - .frame(height: viewModel.viewState == .guestJoin ? 142 : 100) + .frame(height: viewModel.calculateBottomPanelHeight()) } } } diff --git a/iMEGA/CallScene/WaitingRoom/WaitingRoomViewController.swift b/iMEGA/CallScene/WaitingRoom/WaitingRoomViewController.swift index f420a142e1..9f91cf17d4 100644 --- a/iMEGA/CallScene/WaitingRoom/WaitingRoomViewController.swift +++ b/iMEGA/CallScene/WaitingRoom/WaitingRoomViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGAUIKit import SwiftUI @@ -43,10 +44,12 @@ final class WaitingRoomViewController: UIViewController { // MARK: - Private private func configureNavBarTitle() { - navigationItem.titleView = UILabel().customNavBarLabel(title: viewModel.meetingTitle, - titleFont: UIFont.preferredFont(style: .subheadline, weight: .bold), - subtitle: viewModel.meetingDate, - subtitleFont: UIFont.preferredFont(style: .caption1, weight: .regular)) + navigationItem.titleView = UILabel().customNavBarLabel( + title: viewModel.meetingTitle, + titleFont: UIFont.preferredFont(style: .subheadline, weight: .bold), + subtitle: viewModel.createMeetingDate(), + subtitleFont: UIFont.preferredFont(style: .caption1, weight: .regular) + ) navigationItem.titleView?.sizeToFit() } diff --git a/iMEGA/CallScene/WaitingRoom/WaitingRoomViewModel.swift b/iMEGA/CallScene/WaitingRoom/WaitingRoomViewModel.swift index 5688704d78..599c32ff7e 100644 --- a/iMEGA/CallScene/WaitingRoom/WaitingRoomViewModel.swift +++ b/iMEGA/CallScene/WaitingRoom/WaitingRoomViewModel.swift @@ -1,65 +1,436 @@ +import Combine import MEGADomain +import MEGAL10n import MEGAPermissions import MEGAPresentation +import MEGASDKRepo protocol WaitingRoomViewRouting: Routing { - func dismiss() + func dismiss(completion: (() -> Void)?) func showLeaveAlert(leaveAction: @escaping () -> Void) + func showMeetingInfo() + func showVideoPermissionError() + func showAudioPermissionError() + func showHostDenyAlert(leaveAction: @escaping () -> Void) + func hostAllowToJoin() } final class WaitingRoomViewModel: ObservableObject { private let scheduledMeeting: ScheduledMeetingEntity private let router: any WaitingRoomViewRouting + private let chatUseCase: any ChatUseCaseProtocol + private let callUseCase: any CallUseCaseProtocol + private let callCoordinatorUseCase: any CallCoordinatorUseCaseProtocol + private let meetingUseCase: any MeetingCreatingUseCaseProtocol + private let authUseCase: any AuthUseCaseProtocol + private let waitingRoomUseCase: any WaitingRoomUseCaseProtocol private let accountUseCase: any AccountUseCaseProtocol + private let megaHandleUseCase: any MEGAHandleUseCaseProtocol private let userImageUseCase: any UserImageUseCaseProtocol private let localVideoUseCase: any CallLocalVideoUseCaseProtocol + private let captureDeviceUseCase: any CaptureDeviceUseCaseProtocol private let audioSessionUseCase: any AudioSessionUseCaseProtocol private let permissionHandler: any DevicePermissionsHandling - + private let chatLink: String? + var meetingTitle: String { scheduledMeeting.title } - var meetingDate: String { "Mon, 20 May ·10:00-11:00" } enum WaitingRoomViewState { case guestJoin case guestJoining + case waitForHostToStart case waitForHostToLetIn } - @Published var viewState: WaitingRoomViewState + @Published private(set) var viewState: WaitingRoomViewState = .waitForHostToLetIn + @Published private(set) var userAvatar: UIImage? + @Published private(set) var videoImage: UIImage? @Published var isVideoEnabled = false - @Published var isMicrophoneEnabled = true - @Published var isSpeakerEnabled = false + @Published var isMicrophoneEnabled = false + @Published var isSpeakerEnabled = true + @Published var screenSize: CGSize = .zero { + didSet { + guard screenSize != .zero else { return } + isLandscape = screenSize.width > screenSize.height + } + } + + var showWaitingRoomMessage: Bool { + viewState == .waitForHostToStart || viewState == .waitForHostToLetIn + } + var waitingRoomMessage: String { + switch viewState { + case .waitForHostToStart: + return Strings.Localizable.Meetings.WaitingRoom.Message.waitForHostToStartTheMeeting + case .waitForHostToLetIn: + return Strings.Localizable.Meetings.WaitingRoom.Message.waitForHostToLetYouIn + default: + return "" + } + } + + private(set) var isLandscape: Bool = false + + private var call: CallEntity? { + callUseCase.call(for: scheduledMeeting.chatId) + } + private var isMeetingStart: Bool { + call != nil + } + private var isCallActive: Bool { + chatUseCase.isCallActive(for: scheduledMeeting.chatId) + } + + private var appDidBecomeActiveSubscription: AnyCancellable? + private var appWillResignActiveSubscription: AnyCancellable? + private var subscriptions = Set() init(scheduledMeeting: ScheduledMeetingEntity, router: some WaitingRoomViewRouting, + chatUseCase: some ChatUseCaseProtocol, + callUseCase: some CallUseCaseProtocol, + callCoordinatorUseCase: some CallCoordinatorUseCaseProtocol, + meetingUseCase: some MeetingCreatingUseCaseProtocol, + authUseCase: some AuthUseCaseProtocol, + waitingRoomUseCase: some WaitingRoomUseCaseProtocol, accountUseCase: some AccountUseCaseProtocol, + megaHandleUseCase: some MEGAHandleUseCaseProtocol, userImageUseCase: some UserImageUseCaseProtocol, localVideoUseCase: some CallLocalVideoUseCaseProtocol, + captureDeviceUseCase: some CaptureDeviceUseCaseProtocol, audioSessionUseCase: some AudioSessionUseCaseProtocol, - permissionHandler: some DevicePermissionsHandling) { + permissionHandler: some DevicePermissionsHandling, + chatLink: String? = nil) { self.scheduledMeeting = scheduledMeeting self.router = router + self.chatUseCase = chatUseCase + self.callUseCase = callUseCase + self.callCoordinatorUseCase = callCoordinatorUseCase + self.meetingUseCase = meetingUseCase + self.authUseCase = authUseCase + self.waitingRoomUseCase = waitingRoomUseCase self.accountUseCase = accountUseCase + self.megaHandleUseCase = megaHandleUseCase self.userImageUseCase = userImageUseCase self.localVideoUseCase = localVideoUseCase + self.captureDeviceUseCase = captureDeviceUseCase self.audioSessionUseCase = audioSessionUseCase self.permissionHandler = permissionHandler - viewState = accountUseCase.isGuest ? .guestJoin : .waitForHostToLetIn + self.chatLink = chatLink + initializeState() + initSubscriptions() + fetchInitialValues() + } + + deinit { + callUseCase.stopListeningForCall() + } + + // MARK: - Public + + func createMeetingDate() -> String { + let startDate = scheduledMeeting.startDate + let endDate = scheduledMeeting.endDate + + let timeFormatter = DateFormatter.fromTemplate("HH:mm") + + let weekDayString = DateFormatter.fromTemplate("E").localisedString(from: startDate) + let startDateString = DateFormatter.fromTemplate("ddMMM").localisedString(from: startDate) + let startTimeString = timeFormatter.localisedString(from: startDate) + let endTimeString = timeFormatter.localisedString(from: endDate) + + return "\(weekDayString), \(startDateString) ·\(startTimeString)-\(endTimeString)" + } + + func enableLocalVideo(enabled: Bool) { + checkForVideoPermission { + if enabled { + self.localVideoUseCase.openVideoDevice { [weak self] _ in + guard let self else { return } + localVideoUseCase.addLocalVideo(for: MEGAInvalidHandle, callbacksDelegate: self) + } + } else { + self.localVideoUseCase.releaseVideoDevice { [weak self] _ in + guard let self else { return } + localVideoUseCase.removeLocalVideo(for: MEGAInvalidHandle, callbacksDelegate: self) + } + } + } + } + + func enableLocalMicrophone(enabled: Bool) { + checkForAudioPermission {} + } + + func enableLoudSpeaker(enabled: Bool) { + if enabled { + audioSessionUseCase.enableLoudSpeaker { [weak self] _ in + self?.updateSpeakerInfo() + } + } else { + audioSessionUseCase.disableLoudSpeaker { [weak self] _ in + self?.updateSpeakerInfo() + } + } } func leaveButtonTapped() { router.showLeaveAlert { [weak self] in guard let self else { return } - router.dismiss() + dismiss() } } func infoButtonTapped() { - print(#function) + router.showMeetingInfo() } - func tapJoinAction() { + func tapJoinAction(firstName: String, lastName: String) { guard viewState == .guestJoin else { return } viewState = .guestJoining + createEphemeralAccountAndJoinChat(firstName: firstName, lastName: lastName) + } + + func calculateVideoSize() -> CGSize { + let videoAspectRatio = isLandscape ? 424.0 / 236.0 : 236.0 / 424.0 + let videoHeight = screenSize.height - (isLandscape ? 66.0 : 332.0) + let videoWidth = videoHeight * videoAspectRatio + return CGSize(width: videoWidth, height: videoHeight) + } + + func calculateBottomPanelHeight() -> CGFloat { + switch viewState { + case .guestJoin: + return 142.0 + case .guestJoining: + return isLandscape ? 38.0 : 100.0 + case .waitForHostToStart, .waitForHostToLetIn: + return isLandscape ? 8.0 : 100.0 + } + } + + // MARK: - Private + + private func initializeState() { + if accountUseCase.isGuest { + viewState = .guestJoin + } else if isMeetingStart { + viewState = .waitForHostToLetIn + answerCall() + } else { + viewState = .waitForHostToStart + } + } + + private func initSubscriptions() { + appDidBecomeActiveSubscription = NotificationCenter.default + .publisher(for: UIApplication.didBecomeActiveNotification) + .sink { [weak self] _ in + guard let self else { return } + audioSessionUseCase.configureCallAudioSession() + addRouteChangedListener() + enableLoudSpeaker(enabled: isSpeakerEnabled) + } + + appWillResignActiveSubscription = NotificationCenter.default + .publisher(for: UIApplication.willResignActiveNotification) + .sink { [weak self] _ in + self?.removeRouteChangedListener() + } + + chatUseCase + .monitorChatCallStatusUpdate() + .receive(on: DispatchQueue.main) + .sink { [weak self] call in + guard let self, + viewState != .guestJoin && viewState != .guestJoining, + call.chatId == scheduledMeeting.chatId else { return } + if isMeetingStart { + if !isCallActive { + answerCall() + } + } else { + if isCallActive { + dismissCall() + } + viewState = .waitForHostToStart + } + } + .store(in: &subscriptions) + } + + private func fetchInitialValues() { + audioSessionUseCase.configureCallAudioSession() + if audioSessionUseCase.isBluetoothAudioRouteAvailable { + isSpeakerEnabled = audioSessionUseCase.isOutputFrom(port: .builtInSpeaker) + updateSpeakerInfo() + } else { + enableLoudSpeaker(enabled: isSpeakerEnabled) + } + permissionHandler.requestAudioPermission() + selectFrontCameraIfNeeded() + fetchUserAvatar() + } + + private func updateSpeakerInfo() { + let currentSelectedPort = audioSessionUseCase.currentSelectedAudioPort + let isBluetoothAvailable = audioSessionUseCase.isBluetoothAudioRouteAvailable + isSpeakerEnabled = audioSessionUseCase.isOutputFrom(port: .builtInSpeaker) + // Need this debug for the next ticket + MEGALogDebug("Waiting room: updating speaker info with selected port \(currentSelectedPort), bluetooth available \(isBluetoothAvailable), isSpeakerEnabled: \(isSpeakerEnabled)") + } + + private func selectFrontCameraIfNeeded() { + if isBackCameraSelected() { + guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(position: .front) else { + return + } + localVideoUseCase.selectCamera(withLocalizedName: selectCameraLocalizedString) { _ in } + } + } + + private func isBackCameraSelected() -> Bool { + guard let selectCameraLocalizedString = captureDeviceUseCase.wideAngleCameraLocalizedName(position: .back), + localVideoUseCase.videoDeviceSelected() == selectCameraLocalizedString else { + return false + } + return true + } + + private func addRouteChangedListener() { + audioSessionUseCase.routeChanged { [weak self] routeChangedReason, _ in + guard let self = self else { return } + self.sessionRouteChanged(routeChangedReason: routeChangedReason) + } + } + + private func removeRouteChangedListener() { + audioSessionUseCase.routeChanged() + } + + private func sessionRouteChanged(routeChangedReason: AudioSessionRouteChangedReason) { + updateSpeakerInfo() + } + + private func checkForVideoPermission(onSuccess completionBlock: @escaping () -> Void) { + permissionHandler.requestVideoPermission { [weak self] granted in + if granted { + completionBlock() + } else { + self?.router.showVideoPermissionError() + } + } + } + + private func checkForAudioPermission(onSuccess completionBlock: @escaping () -> Void) { + permissionHandler.requestAudioPermission { [weak self] granted in + if granted { + completionBlock() + } else { + self?.router.showAudioPermissionError() + } + } + } + + private func fetchUserAvatar() { + guard let myHandle = accountUseCase.currentUserHandle, + let base64Handle = megaHandleUseCase.base64Handle(forUserHandle: myHandle), + let avatarBackgroundHexColor = userImageUseCase.avatarColorHex(forBase64UserHandle: base64Handle) else { + return + } + userImageUseCase.fetchUserAvatar( + withUserHandle: myHandle, + base64Handle: base64Handle, + avatarBackgroundHexColor: avatarBackgroundHexColor, + name: waitingRoomUseCase.userName() + ) { [weak self] result in + guard let self else { return } + if case let .success(image) = result { + userAvatar = image + } + } + } + + private func disableLocalVideo() { + if isVideoEnabled { + localVideoUseCase.removeLocalVideo(for: MEGAInvalidHandle, callbacksDelegate: self) + } + } + + private func dismiss() { + router.dismiss { [weak self] in + guard let self else { return } + if accountUseCase.isGuest { + authUseCase.logout() + } + disableLocalVideo() + callUseCase.stopListeningForCall() + dismissCall() + } + } + + // MARK: - Chat and call related methods + + private func answerCall() { + callUseCase.answerCall(for: scheduledMeeting.chatId) { [weak self] result in + guard let self else { return } + switch result { + case .success: + viewState = .waitForHostToLetIn + case .failure: + MEGALogDebug("Cannot answer call") + } + } + } + + private func dismissCall() { + guard let call else { return } + callCoordinatorUseCase.removeCallRemovedHandler() + callUseCase.hangCall(for: call.callId) + callCoordinatorUseCase.endCall(call) + } + + private func createEphemeralAccountAndJoinChat(firstName: String, lastName: String) { + guard let chatLink else { return } + meetingUseCase.createEphemeralAccountAndJoinChat(firstName: firstName, lastName: lastName, link: chatLink) { [weak self] result in + guard let self else { return } + if case .success = result { + joinChatCall() + } + } karereInitCompletion: { [weak self] in + guard let self else { return } + if isVideoEnabled { + enableLocalVideo(enabled: true) + } + } + } + + private func joinChatCall() { + meetingUseCase.joinChat(forChatId: scheduledMeeting.chatId, + userHandle: chatUseCase.myUserHandle() + ) { [weak self] result in + guard let self else { return } + switch result { + case .success: + if isMeetingStart { + answerCall() + } else { + viewState = .waitForHostToStart + } + case .failure: + dismiss() + } + } + } +} + +// MARK: - CallLocalVideoCallbacksUseCaseProtocol + +extension WaitingRoomViewModel: CallLocalVideoCallbacksUseCaseProtocol { + func localVideoFrameData(width: Int, height: Int, buffer: Data) { + videoImage = UIImage.mnz_convert(toUIImage: buffer, withWidth: width, withHeight: height) + } + + func localVideoChangedCameraPosition() { } } diff --git a/iMEGA/CallScene/WaitingRoom/WaitingRoomViewRouter.swift b/iMEGA/CallScene/WaitingRoom/WaitingRoomViewRouter.swift index 9656624c82..9a2d9c4acb 100644 --- a/iMEGA/CallScene/WaitingRoom/WaitingRoomViewRouter.swift +++ b/iMEGA/CallScene/WaitingRoom/WaitingRoomViewRouter.swift @@ -1,5 +1,6 @@ import ChatRepo import MEGADomain +import MEGAL10n import MEGAPermissions import MEGARepo import MEGASDKRepo @@ -8,17 +9,27 @@ final class WaitingRoomViewRouter: NSObject, WaitingRoomViewRouting { private(set) var presenter: UIViewController? private let scheduledMeeting: ScheduledMeetingEntity private weak var baseViewController: UIViewController? + private let chatLink: String? - init(presenter: UIViewController?, scheduledMeeting: ScheduledMeetingEntity) { + private var permissionRouter: PermissionAlertRouter { + .makeRouter(deviceHandler: DevicePermissionsHandler.makeHandler()) + } + + init(presenter: UIViewController?, + scheduledMeeting: ScheduledMeetingEntity, + chatLink: String? = nil) { self.presenter = presenter self.scheduledMeeting = scheduledMeeting + self.chatLink = chatLink } + // MARK: - Public + func build() -> UIViewController { - let audioSessionRepository = AudioSessionRepository(audioSession: AVAudioSession.sharedInstance(), callActionManager: CallActionManager.shared) + let audioSessionRepository = AudioSessionRepository(audioSession: .sharedInstance(), callActionManager: .shared) let userImageUseCase = UserImageUseCase( - userImageRepo: UserImageRepository(sdk: MEGASdk.shared), - userStoreRepo: UserStoreRepository(store: MEGAStore.shareInstance()), + userImageRepo: UserImageRepository(sdk: .shared), + userStoreRepo: UserStoreRepository(store: .shareInstance()), thumbnailRepo: ThumbnailRepository.newRepo, fileSystemRepo: FileSystemRepository.newRepo ) @@ -26,11 +37,20 @@ final class WaitingRoomViewRouter: NSObject, WaitingRoomViewRouting { let viewModel = WaitingRoomViewModel( scheduledMeeting: scheduledMeeting, router: self, + chatUseCase: ChatUseCase(chatRepo: ChatRepository.newRepo), + callUseCase: CallUseCase(repository: CallRepository.newRepo), + callCoordinatorUseCase: CallCoordinatorUseCase(), + meetingUseCase: MeetingCreatingUseCase(repository: MeetingCreatingRepository(chatSdk: .shared, sdk: .shared, callActionManager: .shared)), + authUseCase: AuthUseCase(repo: AuthRepository.newRepo, credentialRepo: CredentialRepository.newRepo), + waitingRoomUseCase: WaitingRoomUseCase(waitingRoomRepo: WaitingRoomRepository.newRepo), accountUseCase: AccountUseCase(repository: AccountRepository.newRepo), + megaHandleUseCase: MEGAHandleUseCase(repo: MEGAHandleRepository.newRepo), userImageUseCase: userImageUseCase, - localVideoUseCase: CallLocalVideoUseCase(repository: CallLocalVideoRepository(chatSdk: MEGAChatSdk.shared)), + localVideoUseCase: CallLocalVideoUseCase(repository: CallLocalVideoRepository(chatSdk: .shared)), + captureDeviceUseCase: CaptureDeviceUseCase(repo: CaptureDeviceRepository()), audioSessionUseCase: AudioSessionUseCase(audioSessionRepository: audioSessionRepository), - permissionHandler: DevicePermissionsHandler.makeHandler() + permissionHandler: DevicePermissionsHandler.makeHandler(), + chatLink: chatLink ) let viewController = WaitingRoomViewController(viewModel: viewModel) baseViewController = viewController @@ -45,12 +65,12 @@ final class WaitingRoomViewRouter: NSObject, WaitingRoomViewRouting { presenter.present(nav, animated: true) } - func dismiss() { - baseViewController?.dismiss(animated: true) + func dismiss(completion: (() -> Void)?) { + baseViewController?.dismiss(animated: true, completion: completion) } func showLeaveAlert(leaveAction: @escaping () -> Void) { - guard let baseViewController = baseViewController else { return } + guard let baseViewController else { return } let alertController = UIAlertController(title: Strings.Localizable.Meetings.WaitingRoom.Alert.leaveMeeting, message: nil, preferredStyle: .alert) let leaveAction = UIAlertAction(title: Strings.Localizable.Meetings.WaitingRoom.leave, style: .default) { _ in leaveAction() @@ -61,4 +81,33 @@ final class WaitingRoomViewRouter: NSObject, WaitingRoomViewRouting { alertController.preferredAction = leaveAction baseViewController.present(alertController, animated: true) } + + func showMeetingInfo() { + if let url = URL(string: "https://mega.io/chatandmeetings") { + UIApplication.shared.open(url) + } + } + + func showVideoPermissionError() { + permissionRouter.alertVideoPermission() + } + + func showAudioPermissionError() { + permissionRouter.alertAudioPermission(incomingCall: false) + } + + func showHostDenyAlert(leaveAction: @escaping () -> Void ) { + guard let baseViewController else { return } + let alertController = UIAlertController(title: Strings.Localizable.Meetings.WaitingRoom.Alert.hostDidNotLetYouIn, message: Strings.Localizable.Meetings.WaitingRoom.Alert.youWillBeRemovedFromTheWaitingRoom, preferredStyle: .alert) + let leaveAction = UIAlertAction(title: Strings.Localizable.Meetings.WaitingRoom.Alert.okGotIt, style: .default) { _ in + leaveAction() + } + alertController.addAction(leaveAction) + alertController.preferredAction = leaveAction + baseViewController.present(alertController, animated: true) + } + + func hostAllowToJoin() { + + } } diff --git a/iMEGA/Camera uploads/DataModels/AssetFetchResult.h b/iMEGA/Camera uploads/DataModels/AssetFetchResult.h index 2b96380179..6dc32ab32b 100644 --- a/iMEGA/Camera uploads/DataModels/AssetFetchResult.h +++ b/iMEGA/Camera uploads/DataModels/AssetFetchResult.h @@ -1,4 +1,3 @@ - #import @import Photos; diff --git a/iMEGA/Camera uploads/DataModels/AssetFetchResult.m b/iMEGA/Camera uploads/DataModels/AssetFetchResult.m index 68e8a102a3..c12f92de4a 100644 --- a/iMEGA/Camera uploads/DataModels/AssetFetchResult.m +++ b/iMEGA/Camera uploads/DataModels/AssetFetchResult.m @@ -1,4 +1,3 @@ - #import "AssetFetchResult.h" @implementation AssetFetchResult diff --git a/iMEGA/Camera uploads/DataModels/AssetIdentifierInfo.h b/iMEGA/Camera uploads/DataModels/AssetIdentifierInfo.h index f1fe8afcfb..9e15c56fa4 100644 --- a/iMEGA/Camera uploads/DataModels/AssetIdentifierInfo.h +++ b/iMEGA/Camera uploads/DataModels/AssetIdentifierInfo.h @@ -1,4 +1,3 @@ - #import @import Photos; diff --git a/iMEGA/Camera uploads/DataModels/AssetIdentifierInfo.m b/iMEGA/Camera uploads/DataModels/AssetIdentifierInfo.m index 1ebdf0b469..16393d3b60 100644 --- a/iMEGA/Camera uploads/DataModels/AssetIdentifierInfo.m +++ b/iMEGA/Camera uploads/DataModels/AssetIdentifierInfo.m @@ -1,4 +1,3 @@ - #import "AssetIdentifierInfo.h" @implementation AssetIdentifierInfo diff --git a/iMEGA/Camera uploads/DataModels/AssetLocalAttribute.h b/iMEGA/Camera uploads/DataModels/AssetLocalAttribute.h index 3104a93a82..5eca598815 100644 --- a/iMEGA/Camera uploads/DataModels/AssetLocalAttribute.h +++ b/iMEGA/Camera uploads/DataModels/AssetLocalAttribute.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/DataModels/AssetLocalAttribute.m b/iMEGA/Camera uploads/DataModels/AssetLocalAttribute.m index 649016924d..2132fbd2fc 100644 --- a/iMEGA/Camera uploads/DataModels/AssetLocalAttribute.m +++ b/iMEGA/Camera uploads/DataModels/AssetLocalAttribute.m @@ -1,4 +1,3 @@ - #import "AssetLocalAttribute.h" static NSString * const FingerprintFileName = @"fingerprint"; diff --git a/iMEGA/Camera uploads/DataModels/AssetUploadInfo.h b/iMEGA/Camera uploads/DataModels/AssetUploadInfo.h index 5bfacd51cd..262f005910 100644 --- a/iMEGA/Camera uploads/DataModels/AssetUploadInfo.h +++ b/iMEGA/Camera uploads/DataModels/AssetUploadInfo.h @@ -1,4 +1,3 @@ - #import #import "MEGASdkManager.h" @import Photos; diff --git a/iMEGA/Camera uploads/DataModels/AssetUploadInfo.m b/iMEGA/Camera uploads/DataModels/AssetUploadInfo.m index 2de1faf108..56ce1a3767 100644 --- a/iMEGA/Camera uploads/DataModels/AssetUploadInfo.m +++ b/iMEGA/Camera uploads/DataModels/AssetUploadInfo.m @@ -1,4 +1,3 @@ - #import "AssetUploadInfo.h" @implementation AssetUploadInfo diff --git a/iMEGA/Camera uploads/DataModels/AssetUploadStatus.h b/iMEGA/Camera uploads/DataModels/AssetUploadStatus.h index 621eecbf64..8a7d583f48 100644 --- a/iMEGA/Camera uploads/DataModels/AssetUploadStatus.h +++ b/iMEGA/Camera uploads/DataModels/AssetUploadStatus.h @@ -1,4 +1,3 @@ - #import typedef NS_ENUM(NSInteger, CameraAssetUploadStatus) { diff --git a/iMEGA/Camera uploads/DataModels/AssetUploadStatus.m b/iMEGA/Camera uploads/DataModels/AssetUploadStatus.m index 224aaba304..2d30157af7 100644 --- a/iMEGA/Camera uploads/DataModels/AssetUploadStatus.m +++ b/iMEGA/Camera uploads/DataModels/AssetUploadStatus.m @@ -1,4 +1,3 @@ - #import "AssetUploadStatus.h" @implementation AssetUploadStatus diff --git a/iMEGA/Camera uploads/DataModels/UploadStats.h b/iMEGA/Camera uploads/DataModels/UploadStats.h index 47530f5de6..edbc6bdb5f 100644 --- a/iMEGA/Camera uploads/DataModels/UploadStats.h +++ b/iMEGA/Camera uploads/DataModels/UploadStats.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/DataModels/UploadStats.m b/iMEGA/Camera uploads/DataModels/UploadStats.m index f852919d0e..910b1f8cbf 100644 --- a/iMEGA/Camera uploads/DataModels/UploadStats.m +++ b/iMEGA/Camera uploads/DataModels/UploadStats.m @@ -1,4 +1,3 @@ - #import "UploadStats.h" @implementation UploadStats diff --git a/iMEGA/Camera uploads/Extension/PhotosViewController+ContextMenu.swift b/iMEGA/Camera uploads/Extension/PhotosViewController+ContextMenu.swift index 16d73f1584..6886c9e942 100644 --- a/iMEGA/Camera uploads/Extension/PhotosViewController+ContextMenu.swift +++ b/iMEGA/Camera uploads/Extension/PhotosViewController+ContextMenu.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo import MEGASwift import UIKit diff --git a/iMEGA/Camera uploads/Extension/PhotosViewController+PhotoLibrary.swift b/iMEGA/Camera uploads/Extension/PhotosViewController+PhotoLibrary.swift index a7f64e513d..ba040b83ac 100644 --- a/iMEGA/Camera uploads/Extension/PhotosViewController+PhotoLibrary.swift +++ b/iMEGA/Camera uploads/Extension/PhotosViewController+PhotoLibrary.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n import SwiftUI extension PhotosViewController: PhotoLibraryProvider { @@ -40,10 +41,8 @@ extension PhotosViewController: PhotoLibraryProvider { if count == 0 { message = Strings.Localizable.selectTitle - } else if count == 1 { - message = Strings.Localizable.oneItemSelected(count) } else { - message = Strings.Localizable.itemsSelected(count) + message = Strings.Localizable.General.Format.itemsSelected(count) } objcWrapper_parent.navigationItem.title = message diff --git a/iMEGA/Camera uploads/FilterView/PhotoLibraryFilterView.swift b/iMEGA/Camera uploads/FilterView/PhotoLibraryFilterView.swift index c4b4870a5c..66967ce40a 100644 --- a/iMEGA/Camera uploads/FilterView/PhotoLibraryFilterView.swift +++ b/iMEGA/Camera uploads/FilterView/PhotoLibraryFilterView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct PhotoLibraryFilterView: View { diff --git a/iMEGA/Camera uploads/MEGACollectionViewFlowLayout.h b/iMEGA/Camera uploads/MEGACollectionViewFlowLayout.h index 26d96d96bc..74139abc6e 100644 --- a/iMEGA/Camera uploads/MEGACollectionViewFlowLayout.h +++ b/iMEGA/Camera uploads/MEGACollectionViewFlowLayout.h @@ -1,4 +1,3 @@ - #import @interface MEGACollectionViewFlowLayout : UICollectionViewFlowLayout diff --git a/iMEGA/Camera uploads/MEGACollectionViewFlowLayout.m b/iMEGA/Camera uploads/MEGACollectionViewFlowLayout.m index 1375633785..9759a7f519 100644 --- a/iMEGA/Camera uploads/MEGACollectionViewFlowLayout.m +++ b/iMEGA/Camera uploads/MEGACollectionViewFlowLayout.m @@ -1,4 +1,3 @@ - #import "MEGACollectionViewFlowLayout.h" @implementation MEGACollectionViewFlowLayout diff --git a/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewController+ContextMenu.swift b/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewController+ContextMenu.swift index 901408c454..65d453a2a7 100644 --- a/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewController+ContextMenu.swift +++ b/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewController+ContextMenu.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import SwiftUI import UIKit diff --git a/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewController.swift b/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewController.swift index 2ab5651847..08086297f5 100644 --- a/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewController.swift +++ b/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewController.swift @@ -1,6 +1,8 @@ import Combine import MEGADomain +import MEGAL10n import MEGAPresentation +import MEGASDKRepo import MEGAUIKit import SwiftUI import UIKit @@ -282,7 +284,7 @@ final class PhotoAlbumContainerViewController: UIViewController, TraitEnvironmen } @objc private func shareLinksButtonPressed() { - viewModel.showShareAlbumLinks = true + viewModel.shareLinksTapped() } @objc private func removeLinksButtonPressed() { diff --git a/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewModel.swift b/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewModel.swift index f840c2034d..010bbbcb26 100644 --- a/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewModel.swift +++ b/iMEGA/Camera uploads/PhotoAlbum/PhotoAlbumContainerViewModel.swift @@ -24,4 +24,9 @@ final class PhotoAlbumContainerViewModel: ObservableObject { func didAppear() { tracker.trackAnalyticsEvent(with: PhotoScreenEvent()) } + + func shareLinksTapped() { + showShareAlbumLinks = true + tracker.trackAnalyticsEvent(with: AlbumListShareLinkMenuItemEvent()) + } } diff --git a/iMEGA/Camera uploads/PhotoAlbum/Tabs/PagerTabViewModel.swift b/iMEGA/Camera uploads/PhotoAlbum/Tabs/PagerTabViewModel.swift index 72a5e0f69b..f180129be4 100644 --- a/iMEGA/Camera uploads/PhotoAlbum/Tabs/PagerTabViewModel.swift +++ b/iMEGA/Camera uploads/PhotoAlbum/Tabs/PagerTabViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGAAnalyticsiOS +import MEGAL10n import MEGAPresentation enum PhotoLibraryTab: CaseIterable { diff --git a/iMEGA/Camera uploads/PhotosFilterOptions.swift b/iMEGA/Camera uploads/PhotosFilterOptions.swift index c6669db550..e5e774ab49 100644 --- a/iMEGA/Camera uploads/PhotosFilterOptions.swift +++ b/iMEGA/Camera uploads/PhotosFilterOptions.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n struct PhotosFilterOptionKeys { static let cameraUploadTimeline = "cameraUploadTimeline" diff --git a/iMEGA/Camera uploads/PhotosViewController.h b/iMEGA/Camera uploads/PhotosViewController.h index 2b50fd3314..239b574639 100644 --- a/iMEGA/Camera uploads/PhotosViewController.h +++ b/iMEGA/Camera uploads/PhotosViewController.h @@ -1,4 +1,3 @@ - #import #import "MEGASdkManager.h" diff --git a/iMEGA/Camera uploads/PhotosViewController.m b/iMEGA/Camera uploads/PhotosViewController.m index ee6aea07c7..622006f5e7 100644 --- a/iMEGA/Camera uploads/PhotosViewController.m +++ b/iMEGA/Camera uploads/PhotosViewController.m @@ -26,6 +26,7 @@ #import "UIViewController+MNZCategory.h" #import "NSArray+MNZCategory.h" +@import MEGAL10nObjc; @import StoreKit; @import Photos; @import MEGAUIKit; @@ -59,7 +60,7 @@ @implementation PhotosViewController #pragma mark - Lifecycle - (void)viewDidLoad { [super viewDidLoad]; - [self.enableCameraUploadsButton setTitle:NSLocalizedString(@"enable", nil) forState:UIControlStateNormal]; + [self.enableCameraUploadsButton setTitle:LocalizedString(@"enable", @"") forState:UIControlStateNormal]; [self objcWrapper_configPhotosBannerView]; @@ -235,15 +236,15 @@ - (void)configUploadProgressByStats:(UploadStats *)uploadStats { NSString *progressText; if (uploadStats.pendingFilesCount == 1) { if (CameraUploadManager.isCameraUploadPausedBecauseOfNoWiFiConnection) { - progressText = NSLocalizedString(@"Upload paused because of no WiFi, 1 file pending", nil); + progressText = LocalizedString(@"Upload paused because of no WiFi, 1 file pending", @""); } else { - progressText = NSLocalizedString(@"cameraUploadsPendingFile", @"Message shown while uploading files. Singular."); + progressText = LocalizedString(@"cameraUploadsPendingFile", @"Message shown while uploading files. Singular."); } } else { if (CameraUploadManager.isCameraUploadPausedBecauseOfNoWiFiConnection) { - progressText = [NSString stringWithFormat:NSLocalizedString(@"Upload paused because of no WiFi, %lu files pending", nil), uploadStats.pendingFilesCount]; + progressText = [NSString stringWithFormat:LocalizedString(@"Upload paused because of no WiFi, %lu files pending", @""), uploadStats.pendingFilesCount]; } else { - progressText = [NSString stringWithFormat:NSLocalizedString(@"cameraUploadsPendingFiles", @"Message shown while uploading files. Plural."), uploadStats.pendingFilesCount]; + progressText = [NSString stringWithFormat:LocalizedString(@"cameraUploadsPendingFiles", @"Message shown while uploading files. Plural."), uploadStats.pendingFilesCount]; } } self.photosUploadedLabel.text = progressText; @@ -275,9 +276,9 @@ - (void)loadEnableVideoStateIfNeeded { - (void)configStateLabelByVideoPendingCount:(NSUInteger)count { NSString *videoMessage; if (count == 1) { - videoMessage = NSLocalizedString(@"Photos uploaded, video uploads are off, 1 video not uploaded", nil); + videoMessage = LocalizedString(@"Photos uploaded, video uploads are off, 1 video not uploaded", @""); } else { - videoMessage = [NSString stringWithFormat:NSLocalizedString(@"Photos uploaded, video uploads are off, %lu videos not uploaded", nil), (unsigned long)count]; + videoMessage = [NSString stringWithFormat:LocalizedString(@"Photos uploaded, video uploads are off, %lu videos not uploaded", @""), (unsigned long)count]; } self.stateLabel.text = videoMessage; @@ -296,7 +297,7 @@ - (void)setCurrentState:(MEGACameraUploadsState)currentState { switch (currentState) { case MEGACameraUploadsStateDisabled: - self.stateLabel.text = NSLocalizedString(@"enableCameraUploadsButton", nil); + self.stateLabel.text = LocalizedString(@"enableCameraUploadsButton", @""); self.enableCameraUploadsButton.hidden = NO; break; case MEGACameraUploadsStateUploading: @@ -304,20 +305,20 @@ - (void)setCurrentState:(MEGACameraUploadsState)currentState { self.progressStackView.hidden = NO; break; case MEGACameraUploadsStateCompleted: - self.stateLabel.text = NSLocalizedString(@"cameraUploadsComplete", @"Message shown when the camera uploads have been completed"); + self.stateLabel.text = LocalizedString(@"cameraUploadsComplete", @"Message shown when the camera uploads have been completed"); break; case MEGACameraUploadsStateNoInternetConnection: if ([self.viewModel hasNoPhotos]) { self.stateView.hidden = YES; } else { - self.stateLabel.text = NSLocalizedString(@"noInternetConnection", @"Text shown on the app when you don't have connection to the internet or when you have lost it"); + self.stateLabel.text = LocalizedString(@"noInternetConnection", @"Text shown on the app when you don't have connection to the internet or when you have lost it"); } break; case MEGACameraUploadsStateEmpty: self.stateView.hidden = YES; break; case MEGACameraUploadsStateLoading: - self.stateLabel.text = NSLocalizedString(@"loading", nil); + self.stateLabel.text = LocalizedString(@"loading", @""); break; case MEGACameraUploadsStateEnableVideo: self.stateLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; @@ -352,7 +353,7 @@ - (void)reloadPhotos { } - (void)updateNavigationTitleBar { - self.objcWrapper_parent.navigationItem.title = NSLocalizedString(@"photo.navigation.title", @"Title of one of the Settings sections where you can set up the 'Camera Uploads' options"); + self.objcWrapper_parent.navigationItem.title = LocalizedString(@"photo.navigation.title", @"Title of one of the Settings sections where you can set up the 'Camera Uploads' options"); } - (void)setToolbarActionsEnabled:(BOOL)boolValue { @@ -459,10 +460,10 @@ - (IBAction)deleteAction:(UIBarButtonItem *)sender { - (void)showCameraUploadBoardingScreen { CustomModalAlertViewController *boardingAlertVC = [[CustomModalAlertViewController alloc] init]; boardingAlertVC.image = [UIImage imageNamed:@"cameraUploadsBoarding"]; - boardingAlertVC.viewTitle = NSLocalizedString(@"enableCameraUploadsButton", @"Button title that enables the functionality 'Camera Uploads', which uploads all the photos in your device to MEGA"); - boardingAlertVC.detail = NSLocalizedString(@"Automatically backup your photos and videos to the Cloud Drive.", nil); - boardingAlertVC.firstButtonTitle = NSLocalizedString(@"enable", @"Text button shown when camera upload will be enabled"); - boardingAlertVC.dismissButtonTitle = NSLocalizedString(@"notNow", nil); + boardingAlertVC.viewTitle = LocalizedString(@"enableCameraUploadsButton", @"Button title that enables the functionality 'Camera Uploads', which uploads all the photos in your device to MEGA"); + boardingAlertVC.detail = LocalizedString(@"Automatically backup your photos and videos to the Cloud Drive.", @""); + boardingAlertVC.firstButtonTitle = LocalizedString(@"enable", @"Text button shown when camera upload will be enabled"); + boardingAlertVC.dismissButtonTitle = LocalizedString(@"notNow", @""); boardingAlertVC.firstCompletion = ^{ [self dismissViewControllerAnimated:YES completion:^{ @@ -514,15 +515,15 @@ - (NSString *)titleForEmptyState { if ([MEGAReachabilityManager isReachable]) { if (CameraUploadManager.isCameraUploadEnabled) { if ([self.photosByMonthYearArray count] == 0) { - text = NSLocalizedString(@"cameraUploadsEnabled", nil); + text = LocalizedString(@"cameraUploadsEnabled", @""); } else { return nil; } } else { - text = NSLocalizedString(@"enableCameraUploadsButton", @"Enable Camera Uploads"); + text = LocalizedString(@"enableCameraUploadsButton", @"Enable Camera Uploads"); } } else { - text = NSLocalizedString(@"noInternetConnection", @"No Internet Connection"); + text = LocalizedString(@"noInternetConnection", @"No Internet Connection"); } return text; @@ -531,9 +532,9 @@ - (NSString *)titleForEmptyState { - (NSString *)descriptionForEmptyState { NSString *text = @""; if (MEGAReachabilityManager.isReachable && !CameraUploadManager.isCameraUploadEnabled) { - text = NSLocalizedString(@"Automatically backup your photos and videos to the Cloud Drive.", nil); + text = LocalizedString(@"Automatically backup your photos and videos to the Cloud Drive.", @""); } else if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); + text = LocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); } return text; @@ -560,11 +561,11 @@ - (NSString *)buttonTitleForEmptyState { NSString *text = @""; if ([MEGAReachabilityManager isReachable]) { if (!CameraUploadManager.isCameraUploadEnabled) { - text = NSLocalizedString(@"enable", @"Text button shown when the chat is disabled and if tapped the chat will be enabled"); + text = LocalizedString(@"enable", @"Text button shown when the chat is disabled and if tapped the chat will be enabled"); } } else { if (!MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); + text = LocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); } } diff --git a/iMEGA/Camera uploads/TransferSession/TransferResponseValidator.h b/iMEGA/Camera uploads/TransferSession/TransferResponseValidator.h index 4f06e8749b..272024e975 100644 --- a/iMEGA/Camera uploads/TransferSession/TransferResponseValidator.h +++ b/iMEGA/Camera uploads/TransferSession/TransferResponseValidator.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/TransferSession/TransferResponseValidator.m b/iMEGA/Camera uploads/TransferSession/TransferResponseValidator.m index 6f94d7de25..1705b1f6ba 100644 --- a/iMEGA/Camera uploads/TransferSession/TransferResponseValidator.m +++ b/iMEGA/Camera uploads/TransferSession/TransferResponseValidator.m @@ -1,4 +1,3 @@ - #import "TransferResponseValidator.h" #import "NSError+CameraUpload.h" diff --git a/iMEGA/Camera uploads/TransferSession/TransferSessionDelegate.h b/iMEGA/Camera uploads/TransferSession/TransferSessionDelegate.h index 99b4e3c029..bc8e42c008 100644 --- a/iMEGA/Camera uploads/TransferSession/TransferSessionDelegate.h +++ b/iMEGA/Camera uploads/TransferSession/TransferSessionDelegate.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/TransferSession/TransferSessionDelegate.m b/iMEGA/Camera uploads/TransferSession/TransferSessionDelegate.m index dfda9d54b5..7189083624 100644 --- a/iMEGA/Camera uploads/TransferSession/TransferSessionDelegate.m +++ b/iMEGA/Camera uploads/TransferSession/TransferSessionDelegate.m @@ -1,4 +1,3 @@ - #import "TransferSessionDelegate.h" #import "TransferSessionTaskDelegate.h" #import "TransferSessionManager.h" diff --git a/iMEGA/Camera uploads/TransferSession/TransferSessionManager.h b/iMEGA/Camera uploads/TransferSession/TransferSessionManager.h index 24d4688a58..7aee4b705d 100644 --- a/iMEGA/Camera uploads/TransferSession/TransferSessionManager.h +++ b/iMEGA/Camera uploads/TransferSession/TransferSessionManager.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/TransferSession/TransferSessionManager.m b/iMEGA/Camera uploads/TransferSession/TransferSessionManager.m index 50076c1345..eaf209edc9 100644 --- a/iMEGA/Camera uploads/TransferSession/TransferSessionManager.m +++ b/iMEGA/Camera uploads/TransferSession/TransferSessionManager.m @@ -1,4 +1,3 @@ - #import "TransferSessionManager.h" #import "TransferSessionDelegate.h" #import "TransferSessionTaskDelegate.h" diff --git a/iMEGA/Camera uploads/TransferSession/TransferSessionTaskDelegate.h b/iMEGA/Camera uploads/TransferSession/TransferSessionTaskDelegate.h index b357a4683c..c2b0bc3be4 100644 --- a/iMEGA/Camera uploads/TransferSession/TransferSessionTaskDelegate.h +++ b/iMEGA/Camera uploads/TransferSession/TransferSessionTaskDelegate.h @@ -1,4 +1,3 @@ - #import #import "TransferSessionManager.h" diff --git a/iMEGA/Camera uploads/TransferSession/TransferSessionTaskDelegate.m b/iMEGA/Camera uploads/TransferSession/TransferSessionTaskDelegate.m index 14565eb5b5..6bf547e974 100644 --- a/iMEGA/Camera uploads/TransferSession/TransferSessionTaskDelegate.m +++ b/iMEGA/Camera uploads/TransferSession/TransferSessionTaskDelegate.m @@ -1,4 +1,3 @@ - #import "TransferSessionTaskDelegate.h" #import "CameraUploadCompletionManager.h" #import "TransferResponseValidator.h" diff --git a/iMEGA/Camera uploads/UploadManagers/AttributeUploadManager.h b/iMEGA/Camera uploads/UploadManagers/AttributeUploadManager.h index 144a9112bc..86ba834974 100644 --- a/iMEGA/Camera uploads/UploadManagers/AttributeUploadManager.h +++ b/iMEGA/Camera uploads/UploadManagers/AttributeUploadManager.h @@ -1,4 +1,3 @@ - #import #import "AssetLocalAttribute.h" #import "AssetUploadInfo.h" diff --git a/iMEGA/Camera uploads/UploadManagers/AttributeUploadManager.m b/iMEGA/Camera uploads/UploadManagers/AttributeUploadManager.m index 782cff30be..eab763ee5d 100644 --- a/iMEGA/Camera uploads/UploadManagers/AttributeUploadManager.m +++ b/iMEGA/Camera uploads/UploadManagers/AttributeUploadManager.m @@ -1,4 +1,3 @@ - #import "AttributeUploadManager.h" #import "NSFileManager+MNZCategory.h" #import "NSURL+CameraUpload.h" diff --git a/iMEGA/Camera uploads/UploadManagers/CameraUploadCompletionManager.h b/iMEGA/Camera uploads/UploadManagers/CameraUploadCompletionManager.h index ce07c1c1d2..f2223e18a9 100644 --- a/iMEGA/Camera uploads/UploadManagers/CameraUploadCompletionManager.h +++ b/iMEGA/Camera uploads/UploadManagers/CameraUploadCompletionManager.h @@ -1,4 +1,3 @@ - #import #import "CameraUploadRecordManager.h" diff --git a/iMEGA/Camera uploads/UploadManagers/CameraUploadCompletionManager.m b/iMEGA/Camera uploads/UploadManagers/CameraUploadCompletionManager.m index 7e99e71b05..f665a8a356 100644 --- a/iMEGA/Camera uploads/UploadManagers/CameraUploadCompletionManager.m +++ b/iMEGA/Camera uploads/UploadManagers/CameraUploadCompletionManager.m @@ -1,4 +1,3 @@ - #import "CameraUploadCompletionManager.h" #import "PutNodeOperation.h" #import "AttributeUploadManager.h" diff --git a/iMEGA/Camera uploads/UploadManagers/CameraUploadManager+Settings.h b/iMEGA/Camera uploads/UploadManagers/CameraUploadManager+Settings.h index 52b9727b37..7e4b6a0914 100644 --- a/iMEGA/Camera uploads/UploadManagers/CameraUploadManager+Settings.h +++ b/iMEGA/Camera uploads/UploadManagers/CameraUploadManager+Settings.h @@ -1,4 +1,3 @@ - #import "CameraUploadManager.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadManagers/CameraUploadManager+Settings.m b/iMEGA/Camera uploads/UploadManagers/CameraUploadManager+Settings.m index de4fce68e1..e14cce5b6a 100644 --- a/iMEGA/Camera uploads/UploadManagers/CameraUploadManager+Settings.m +++ b/iMEGA/Camera uploads/UploadManagers/CameraUploadManager+Settings.m @@ -1,4 +1,3 @@ - #import "CameraUploadManager+Settings.h" #import diff --git a/iMEGA/Camera uploads/UploadManagers/CameraUploadManager.h b/iMEGA/Camera uploads/UploadManagers/CameraUploadManager.h index da024157f4..5e0e30ed44 100644 --- a/iMEGA/Camera uploads/UploadManagers/CameraUploadManager.h +++ b/iMEGA/Camera uploads/UploadManagers/CameraUploadManager.h @@ -1,4 +1,3 @@ - #import #import "UploadStats.h" diff --git a/iMEGA/Camera uploads/UploadManagers/CameraUploadManager.m b/iMEGA/Camera uploads/UploadManagers/CameraUploadManager.m index 24a09d971f..dfb9e68e80 100644 --- a/iMEGA/Camera uploads/UploadManagers/CameraUploadManager.m +++ b/iMEGA/Camera uploads/UploadManagers/CameraUploadManager.m @@ -1,4 +1,3 @@ - #import "CameraUploadManager.h" #import "CameraUploadRecordManager.h" #import "CameraScanner.h" diff --git a/iMEGA/Camera uploads/UploadManagers/CameraUploadRecordManager.h b/iMEGA/Camera uploads/UploadManagers/CameraUploadRecordManager.h index e2b6b1d9fa..7e2179499f 100644 --- a/iMEGA/Camera uploads/UploadManagers/CameraUploadRecordManager.h +++ b/iMEGA/Camera uploads/UploadManagers/CameraUploadRecordManager.h @@ -1,4 +1,3 @@ - #import #import "MOAssetUploadRecord+CoreDataClass.h" #import "AssetUploadStatus.h" diff --git a/iMEGA/Camera uploads/UploadManagers/CameraUploadRecordManager.m b/iMEGA/Camera uploads/UploadManagers/CameraUploadRecordManager.m index b8337c688c..b1050845bc 100644 --- a/iMEGA/Camera uploads/UploadManagers/CameraUploadRecordManager.m +++ b/iMEGA/Camera uploads/UploadManagers/CameraUploadRecordManager.m @@ -1,4 +1,3 @@ - #import "CameraUploadRecordManager.h" #import "MOAssetUploadErrorPerLaunch+CoreDataClass.h" #import "MOAssetUploadErrorPerLogin+CoreDataClass.h" diff --git a/iMEGA/Camera uploads/UploadManagers/ImageExportManager.h b/iMEGA/Camera uploads/UploadManagers/ImageExportManager.h index 4290b495af..0cf748df63 100644 --- a/iMEGA/Camera uploads/UploadManagers/ImageExportManager.h +++ b/iMEGA/Camera uploads/UploadManagers/ImageExportManager.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadManagers/ImageExportManager.m b/iMEGA/Camera uploads/UploadManagers/ImageExportManager.m index 89b1c810b1..51bc0fe3c2 100644 --- a/iMEGA/Camera uploads/UploadManagers/ImageExportManager.m +++ b/iMEGA/Camera uploads/UploadManagers/ImageExportManager.m @@ -1,4 +1,3 @@ - #import "ImageExportManager.h" #import "ImageExportOperation.h" @import CoreServices; diff --git a/iMEGA/Camera uploads/UploadOperations/AssetResourceUploadOperation.h b/iMEGA/Camera uploads/UploadOperations/AssetResourceUploadOperation.h index 1e30ba52c3..7470a5dd7b 100644 --- a/iMEGA/Camera uploads/UploadOperations/AssetResourceUploadOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/AssetResourceUploadOperation.h @@ -1,4 +1,3 @@ - #import "CameraUploadOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/AssetResourceUploadOperation.m b/iMEGA/Camera uploads/UploadOperations/AssetResourceUploadOperation.m index 1ebfeb9c15..60874490c2 100644 --- a/iMEGA/Camera uploads/UploadOperations/AssetResourceUploadOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/AssetResourceUploadOperation.m @@ -1,4 +1,3 @@ - #import "AssetResourceUploadOperation.h" #import "PHAssetResource+CameraUpload.h" #import "CameraUploadOperation+Utils.h" diff --git a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/AttributeUploadOperation.h b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/AttributeUploadOperation.h index ffcf823e5c..4b899afbe0 100644 --- a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/AttributeUploadOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/AttributeUploadOperation.h @@ -1,4 +1,3 @@ - #import "MEGAOperation.h" #import "MEGASdkManager.h" diff --git a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/AttributeUploadOperation.m b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/AttributeUploadOperation.m index 31636ab545..a3e7904aa2 100644 --- a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/AttributeUploadOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/AttributeUploadOperation.m @@ -1,4 +1,3 @@ - #import "AttributeUploadOperation.h" @implementation AttributeUploadOperation diff --git a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/PreviewUploadOperation.h b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/PreviewUploadOperation.h index aa4587f929..2a62d40c54 100644 --- a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/PreviewUploadOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/PreviewUploadOperation.h @@ -1,4 +1,3 @@ - #import "AttributeUploadOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/PreviewUploadOperation.m b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/PreviewUploadOperation.m index 8c8b4b84a8..5d9cc514cf 100644 --- a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/PreviewUploadOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/PreviewUploadOperation.m @@ -1,4 +1,3 @@ - #import "PreviewUploadOperation.h" #import "CameraUploadRequestDelegate.h" #import "NSFileManager+MNZCategory.h" diff --git a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/ThumbnailUploadOperation.h b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/ThumbnailUploadOperation.h index d6a24fd91b..9a0125194c 100644 --- a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/ThumbnailUploadOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/ThumbnailUploadOperation.h @@ -1,4 +1,3 @@ - #import "AttributeUploadOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/ThumbnailUploadOperation.m b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/ThumbnailUploadOperation.m index 6ae19d535c..b37f17539e 100644 --- a/iMEGA/Camera uploads/UploadOperations/AttributeOperations/ThumbnailUploadOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/AttributeOperations/ThumbnailUploadOperation.m @@ -1,4 +1,3 @@ - #import "ThumbnailUploadOperation.h" #import "CameraUploadRequestDelegate.h" #import "MEGAError+MNZCategory.h" diff --git a/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation+Utils.h b/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation+Utils.h index 48ba743212..b38a081f5f 100644 --- a/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation+Utils.h +++ b/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation+Utils.h @@ -1,4 +1,3 @@ - #import "CameraUploadOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation+Utils.m b/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation+Utils.m index bea542ea5e..5146ac659f 100644 --- a/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation+Utils.m +++ b/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation+Utils.m @@ -1,4 +1,3 @@ - #import "CameraUploadOperation+Utils.h" #import "CameraUploadRecordManager.h" #import "NSString+MNZCategory.h" diff --git a/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation.h b/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation.h index 1fd5f3beff..052c2149ba 100644 --- a/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation.h @@ -1,4 +1,3 @@ - #import #import "MEGAOperation.h" #import "AssetUploadInfo.h" diff --git a/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation.m b/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation.m index 06bbbad59e..4f6ad8fa7b 100644 --- a/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/CameraUploadOperation.m @@ -1,4 +1,3 @@ - #import "CameraUploadOperation.h" #import "MEGASdkManager.h" #import "NSFileManager+MNZCategory.h" diff --git a/iMEGA/Camera uploads/UploadOperations/ImageExportOperation.h b/iMEGA/Camera uploads/UploadOperations/ImageExportOperation.h index 8704b372ad..a85fe177c5 100644 --- a/iMEGA/Camera uploads/UploadOperations/ImageExportOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/ImageExportOperation.h @@ -1,4 +1,3 @@ - #import "MEGAOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/ImageExportOperation.m b/iMEGA/Camera uploads/UploadOperations/ImageExportOperation.m index 2c8e86ef00..296f3cc63c 100644 --- a/iMEGA/Camera uploads/UploadOperations/ImageExportOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/ImageExportOperation.m @@ -1,4 +1,3 @@ - #import "ImageExportOperation.h" #import "ImageExporter.h" diff --git a/iMEGA/Camera uploads/UploadOperations/LivePhotoUploadOperation.h b/iMEGA/Camera uploads/UploadOperations/LivePhotoUploadOperation.h index 2db28b4b74..754413adb8 100644 --- a/iMEGA/Camera uploads/UploadOperations/LivePhotoUploadOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/LivePhotoUploadOperation.h @@ -1,4 +1,3 @@ - #import "AssetResourceUploadOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/LivePhotoUploadOperation.m b/iMEGA/Camera uploads/UploadOperations/LivePhotoUploadOperation.m index d3031c7e5f..ee69c2985a 100644 --- a/iMEGA/Camera uploads/UploadOperations/LivePhotoUploadOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/LivePhotoUploadOperation.m @@ -1,4 +1,3 @@ - #import "LivePhotoUploadOperation.h" #import "CameraUploadOperation+Utils.h" #import "PHAssetResource+CameraUpload.h" diff --git a/iMEGA/Camera uploads/UploadOperations/LoadMediaInfoOperation.h b/iMEGA/Camera uploads/UploadOperations/LoadMediaInfoOperation.h index 81d6498c2d..4a67983012 100644 --- a/iMEGA/Camera uploads/UploadOperations/LoadMediaInfoOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/LoadMediaInfoOperation.h @@ -1,4 +1,3 @@ - #import "MEGAExpirableOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/LoadMediaInfoOperation.m b/iMEGA/Camera uploads/UploadOperations/LoadMediaInfoOperation.m index ae347a30aa..cc0733e1bc 100644 --- a/iMEGA/Camera uploads/UploadOperations/LoadMediaInfoOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/LoadMediaInfoOperation.m @@ -1,4 +1,3 @@ - #import "LoadMediaInfoOperation.h" #import "MEGASdkManager.h" diff --git a/iMEGA/Camera uploads/UploadOperations/MEGABackgroundTaskOperation.h b/iMEGA/Camera uploads/UploadOperations/MEGABackgroundTaskOperation.h index f508640d47..14a1f84692 100644 --- a/iMEGA/Camera uploads/UploadOperations/MEGABackgroundTaskOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/MEGABackgroundTaskOperation.h @@ -1,4 +1,3 @@ - #import "MEGAOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/MEGABackgroundTaskOperation.m b/iMEGA/Camera uploads/UploadOperations/MEGABackgroundTaskOperation.m index 385099f7e9..cae3c140f7 100644 --- a/iMEGA/Camera uploads/UploadOperations/MEGABackgroundTaskOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/MEGABackgroundTaskOperation.m @@ -1,4 +1,3 @@ - #import "MEGABackgroundTaskOperation.h" @interface MEGABackgroundTaskOperation () diff --git a/iMEGA/Camera uploads/UploadOperations/MEGAExpirableOperation.h b/iMEGA/Camera uploads/UploadOperations/MEGAExpirableOperation.h index d736a34e97..242a89ba00 100644 --- a/iMEGA/Camera uploads/UploadOperations/MEGAExpirableOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/MEGAExpirableOperation.h @@ -1,4 +1,3 @@ - #import "MEGAOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/MEGAExpirableOperation.m b/iMEGA/Camera uploads/UploadOperations/MEGAExpirableOperation.m index 3264081157..35316bce1d 100644 --- a/iMEGA/Camera uploads/UploadOperations/MEGAExpirableOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/MEGAExpirableOperation.m @@ -1,4 +1,3 @@ - #import "MEGAExpirableOperation.h" @interface MEGAExpirableOperation () diff --git a/iMEGA/Camera uploads/UploadOperations/MEGAOperation.h b/iMEGA/Camera uploads/UploadOperations/MEGAOperation.h index 4e625ccd3f..2a94f684c1 100644 --- a/iMEGA/Camera uploads/UploadOperations/MEGAOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/MEGAOperation.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/MEGAOperation.m b/iMEGA/Camera uploads/UploadOperations/MEGAOperation.m index 5fa8e9451a..9f6845baa5 100644 --- a/iMEGA/Camera uploads/UploadOperations/MEGAOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/MEGAOperation.m @@ -1,4 +1,3 @@ - #import "MEGAOperation.h" @implementation MEGAOperation diff --git a/iMEGA/Camera uploads/UploadOperations/NodesFetchListenerOperation.h b/iMEGA/Camera uploads/UploadOperations/NodesFetchListenerOperation.h index af374b89cf..04406974d6 100644 --- a/iMEGA/Camera uploads/UploadOperations/NodesFetchListenerOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/NodesFetchListenerOperation.h @@ -1,4 +1,3 @@ - #import "MEGAOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/NodesFetchListenerOperation.m b/iMEGA/Camera uploads/UploadOperations/NodesFetchListenerOperation.m index 115350385e..070d2cbb79 100644 --- a/iMEGA/Camera uploads/UploadOperations/NodesFetchListenerOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/NodesFetchListenerOperation.m @@ -1,4 +1,3 @@ - #import "NodesFetchListenerOperation.h" #import "CameraUploadManager.h" diff --git a/iMEGA/Camera uploads/UploadOperations/PhotoUploadOperation.h b/iMEGA/Camera uploads/UploadOperations/PhotoUploadOperation.h index 49fcb1787a..3a545aab70 100644 --- a/iMEGA/Camera uploads/UploadOperations/PhotoUploadOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/PhotoUploadOperation.h @@ -1,4 +1,3 @@ - #import "CameraUploadOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/PhotoUploadOperation.m b/iMEGA/Camera uploads/UploadOperations/PhotoUploadOperation.m index 4c35b90161..bd4ebcf657 100644 --- a/iMEGA/Camera uploads/UploadOperations/PhotoUploadOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/PhotoUploadOperation.m @@ -1,4 +1,3 @@ - #import "PhotoUploadOperation.h" #import "NSFileManager+MNZCategory.h" #import "CameraUploadManager+Settings.h" diff --git a/iMEGA/Camera uploads/UploadOperations/PutNodeOperation.h b/iMEGA/Camera uploads/UploadOperations/PutNodeOperation.h index 8777b7620b..5e514216be 100644 --- a/iMEGA/Camera uploads/UploadOperations/PutNodeOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/PutNodeOperation.h @@ -1,4 +1,3 @@ - #import "AssetUploadInfo.h" #import "MEGASdkManager.h" #import "MEGABackgroundTaskOperation.h" diff --git a/iMEGA/Camera uploads/UploadOperations/PutNodeOperation.m b/iMEGA/Camera uploads/UploadOperations/PutNodeOperation.m index fbc92c2741..da93356c1a 100644 --- a/iMEGA/Camera uploads/UploadOperations/PutNodeOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/PutNodeOperation.m @@ -1,4 +1,3 @@ - #import "PutNodeOperation.h" #import "CameraUploadRequestDelegate.h" #import "NSString+MNZCategory.h" diff --git a/iMEGA/Camera uploads/UploadOperations/VideoUploadOperation.h b/iMEGA/Camera uploads/UploadOperations/VideoUploadOperation.h index b3d64eb007..68e6bf1ec4 100644 --- a/iMEGA/Camera uploads/UploadOperations/VideoUploadOperation.h +++ b/iMEGA/Camera uploads/UploadOperations/VideoUploadOperation.h @@ -1,4 +1,3 @@ - #import "CameraUploadOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadOperations/VideoUploadOperation.m b/iMEGA/Camera uploads/UploadOperations/VideoUploadOperation.m index 874ec88efb..009945f059 100644 --- a/iMEGA/Camera uploads/UploadOperations/VideoUploadOperation.m +++ b/iMEGA/Camera uploads/UploadOperations/VideoUploadOperation.m @@ -1,4 +1,3 @@ - #import "VideoUploadOperation.h" #import "NSFileManager+MNZCategory.h" #import "CameraUploadManager+Settings.h" diff --git a/iMEGA/Camera uploads/UploadUtils/BackgroundUploadingTaskMonitor.h b/iMEGA/Camera uploads/UploadUtils/BackgroundUploadingTaskMonitor.h index 4c69e170e6..231f9e7a09 100644 --- a/iMEGA/Camera uploads/UploadUtils/BackgroundUploadingTaskMonitor.h +++ b/iMEGA/Camera uploads/UploadUtils/BackgroundUploadingTaskMonitor.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/BackgroundUploadingTaskMonitor.m b/iMEGA/Camera uploads/UploadUtils/BackgroundUploadingTaskMonitor.m index f99e2c2180..250b494f3c 100644 --- a/iMEGA/Camera uploads/UploadUtils/BackgroundUploadingTaskMonitor.m +++ b/iMEGA/Camera uploads/UploadUtils/BackgroundUploadingTaskMonitor.m @@ -1,4 +1,3 @@ - #import "BackgroundUploadingTaskMonitor.h" #import "CameraUploadRecordManager.h" diff --git a/iMEGA/Camera uploads/UploadUtils/CameraScanner.h b/iMEGA/Camera uploads/UploadUtils/CameraScanner.h index b4cdb7256e..eddaf849b1 100644 --- a/iMEGA/Camera uploads/UploadUtils/CameraScanner.h +++ b/iMEGA/Camera uploads/UploadUtils/CameraScanner.h @@ -1,4 +1,3 @@ - #import @import Photos; diff --git a/iMEGA/Camera uploads/UploadUtils/CameraScanner.m b/iMEGA/Camera uploads/UploadUtils/CameraScanner.m index 515671e0a1..5f83b6cebc 100644 --- a/iMEGA/Camera uploads/UploadUtils/CameraScanner.m +++ b/iMEGA/Camera uploads/UploadUtils/CameraScanner.m @@ -1,4 +1,3 @@ - #import "CameraScanner.h" #import "CameraUploadRecordManager.h" #import "MOAssetUploadRecord+CoreDataClass.h" diff --git a/iMEGA/Camera uploads/UploadUtils/CameraUploadBackgroundRefreshPerformer.h b/iMEGA/Camera uploads/UploadUtils/CameraUploadBackgroundRefreshPerformer.h index e7335c722c..e0e163098a 100644 --- a/iMEGA/Camera uploads/UploadUtils/CameraUploadBackgroundRefreshPerformer.h +++ b/iMEGA/Camera uploads/UploadUtils/CameraUploadBackgroundRefreshPerformer.h @@ -1,4 +1,3 @@ - #import @import BackgroundTasks; diff --git a/iMEGA/Camera uploads/UploadUtils/CameraUploadBackgroundRefreshPerformer.m b/iMEGA/Camera uploads/UploadUtils/CameraUploadBackgroundRefreshPerformer.m index 47f02723e6..bc5c98a578 100644 --- a/iMEGA/Camera uploads/UploadUtils/CameraUploadBackgroundRefreshPerformer.m +++ b/iMEGA/Camera uploads/UploadUtils/CameraUploadBackgroundRefreshPerformer.m @@ -1,4 +1,3 @@ - #import "CameraUploadBackgroundRefreshPerformer.h" #import "CameraUploadManager.h" #import "CameraUploadManager+Settings.h" diff --git a/iMEGA/Camera uploads/UploadUtils/CameraUploadConcurrentCountCalculator.h b/iMEGA/Camera uploads/UploadUtils/CameraUploadConcurrentCountCalculator.h index e424ae9cfd..3040ffd2f2 100644 --- a/iMEGA/Camera uploads/UploadUtils/CameraUploadConcurrentCountCalculator.h +++ b/iMEGA/Camera uploads/UploadUtils/CameraUploadConcurrentCountCalculator.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/CameraUploadConcurrentCountCalculator.m b/iMEGA/Camera uploads/UploadUtils/CameraUploadConcurrentCountCalculator.m index 757ab5700b..3de0ec27c7 100644 --- a/iMEGA/Camera uploads/UploadUtils/CameraUploadConcurrentCountCalculator.m +++ b/iMEGA/Camera uploads/UploadUtils/CameraUploadConcurrentCountCalculator.m @@ -1,4 +1,3 @@ - #import "CameraUploadConcurrentCountCalculator.h" @interface CameraUploadConcurrentCountCalculator () diff --git a/iMEGA/Camera uploads/UploadUtils/CameraUploadRequestDelegate.h b/iMEGA/Camera uploads/UploadUtils/CameraUploadRequestDelegate.h index e7ad1a89db..1592780505 100644 --- a/iMEGA/Camera uploads/UploadUtils/CameraUploadRequestDelegate.h +++ b/iMEGA/Camera uploads/UploadUtils/CameraUploadRequestDelegate.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/CameraUploadRequestDelegate.m b/iMEGA/Camera uploads/UploadUtils/CameraUploadRequestDelegate.m index 592fe217b4..078c6a6714 100644 --- a/iMEGA/Camera uploads/UploadUtils/CameraUploadRequestDelegate.m +++ b/iMEGA/Camera uploads/UploadUtils/CameraUploadRequestDelegate.m @@ -1,4 +1,3 @@ - #import "CameraUploadRequestDelegate.h" @interface CameraUploadRequestDelegate () diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/AVAsset+CameraUpload.h b/iMEGA/Camera uploads/UploadUtils/Categories/AVAsset+CameraUpload.h index cbb6dc8e9e..1d1e63d7f9 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/AVAsset+CameraUpload.h +++ b/iMEGA/Camera uploads/UploadUtils/Categories/AVAsset+CameraUpload.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/AVAsset+CameraUpload.m b/iMEGA/Camera uploads/UploadUtils/Categories/AVAsset+CameraUpload.m index 3bf0df0d2e..38ced3411c 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/AVAsset+CameraUpload.m +++ b/iMEGA/Camera uploads/UploadUtils/Categories/AVAsset+CameraUpload.m @@ -1,4 +1,3 @@ - #import "AVAsset+CameraUpload.h" static NSString * const HEVCCodec = @"hvc1"; diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/AVURLAsset+CameraUpload.h b/iMEGA/Camera uploads/UploadUtils/Categories/AVURLAsset+CameraUpload.h index b6f8db0b2a..1ccfc1200d 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/AVURLAsset+CameraUpload.h +++ b/iMEGA/Camera uploads/UploadUtils/Categories/AVURLAsset+CameraUpload.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/AVURLAsset+CameraUpload.m b/iMEGA/Camera uploads/UploadUtils/Categories/AVURLAsset+CameraUpload.m index 3958996135..888802d1c6 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/AVURLAsset+CameraUpload.m +++ b/iMEGA/Camera uploads/UploadUtils/Categories/AVURLAsset+CameraUpload.m @@ -1,4 +1,3 @@ - #import "AVURLAsset+CameraUpload.h" @import CoreServices; diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/NSError+CameraUpload.h b/iMEGA/Camera uploads/UploadUtils/Categories/NSError+CameraUpload.h index 3972037978..23e7deeffc 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/NSError+CameraUpload.h +++ b/iMEGA/Camera uploads/UploadUtils/Categories/NSError+CameraUpload.h @@ -1,4 +1,3 @@ - #import @import Photos; diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/NSError+CameraUpload.m b/iMEGA/Camera uploads/UploadUtils/Categories/NSError+CameraUpload.m index a8bad55876..999a67adc9 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/NSError+CameraUpload.m +++ b/iMEGA/Camera uploads/UploadUtils/Categories/NSError+CameraUpload.m @@ -1,4 +1,3 @@ - #import "NSError+CameraUpload.h" NSString * const CameraUploadErrorDomain = @"nz.mega.cameraUpload"; diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/NSURL+CameraUpload.h b/iMEGA/Camera uploads/UploadUtils/Categories/NSURL+CameraUpload.h index f05e06b5af..391a9c4829 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/NSURL+CameraUpload.h +++ b/iMEGA/Camera uploads/UploadUtils/Categories/NSURL+CameraUpload.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/NSURL+CameraUpload.m b/iMEGA/Camera uploads/UploadUtils/Categories/NSURL+CameraUpload.m index 5c447b5ab5..82260d0d5a 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/NSURL+CameraUpload.m +++ b/iMEGA/Camera uploads/UploadUtils/Categories/NSURL+CameraUpload.m @@ -1,4 +1,3 @@ - #import "NSURL+CameraUpload.h" #import "NSString+MNZCategory.h" #import "NSFileManager+MNZCategory.h" diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/PHAssetResource+CameraUpload.h b/iMEGA/Camera uploads/UploadUtils/Categories/PHAssetResource+CameraUpload.h index 4065ee5494..748a09717b 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/PHAssetResource+CameraUpload.h +++ b/iMEGA/Camera uploads/UploadUtils/Categories/PHAssetResource+CameraUpload.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/PHAssetResource+CameraUpload.m b/iMEGA/Camera uploads/UploadUtils/Categories/PHAssetResource+CameraUpload.m index dbb136c125..7aca7f7c35 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/PHAssetResource+CameraUpload.m +++ b/iMEGA/Camera uploads/UploadUtils/Categories/PHAssetResource+CameraUpload.m @@ -1,4 +1,3 @@ - #import "PHAssetResource+CameraUpload.h" @implementation PHAssetResource (CameraUpload) diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchOptions+CameraUpload.h b/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchOptions+CameraUpload.h index 82d497a219..f096284899 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchOptions+CameraUpload.h +++ b/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchOptions+CameraUpload.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchOptions+CameraUpload.m b/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchOptions+CameraUpload.m index fb593141bd..2f74c8b7a5 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchOptions+CameraUpload.m +++ b/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchOptions+CameraUpload.m @@ -1,4 +1,3 @@ - #import "PHFetchOptions+CameraUpload.h" #import "CameraUploadManager+Settings.h" diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchResult+CameraUpload.h b/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchResult+CameraUpload.h index 9080f91bba..0c22e54861 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchResult+CameraUpload.h +++ b/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchResult+CameraUpload.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchResult+CameraUpload.m b/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchResult+CameraUpload.m index 5c3d5ccee3..c2b96af794 100644 --- a/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchResult+CameraUpload.m +++ b/iMEGA/Camera uploads/UploadUtils/Categories/PHFetchResult+CameraUpload.m @@ -1,4 +1,3 @@ - #import "PHFetchResult+CameraUpload.h" #import "MOAssetUploadRecord+CoreDataClass.h" #import "SavedIdentifierParser.h" diff --git a/iMEGA/Camera uploads/UploadUtils/DiskSpaceDetector.h b/iMEGA/Camera uploads/UploadUtils/DiskSpaceDetector.h index b18958cdd1..34f5853949 100644 --- a/iMEGA/Camera uploads/UploadUtils/DiskSpaceDetector.h +++ b/iMEGA/Camera uploads/UploadUtils/DiskSpaceDetector.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/DiskSpaceDetector.m b/iMEGA/Camera uploads/UploadUtils/DiskSpaceDetector.m index 72cfb49737..064a948ea5 100644 --- a/iMEGA/Camera uploads/UploadUtils/DiskSpaceDetector.m +++ b/iMEGA/Camera uploads/UploadUtils/DiskSpaceDetector.m @@ -1,4 +1,3 @@ - #import "DiskSpaceDetector.h" #import "CameraUploadManager.h" #import "NSFileManager+MNZCategory.h" diff --git a/iMEGA/Camera uploads/UploadUtils/FileEncrypter.h b/iMEGA/Camera uploads/UploadUtils/FileEncrypter.h index afd9905fac..7479c2177e 100644 --- a/iMEGA/Camera uploads/UploadUtils/FileEncrypter.h +++ b/iMEGA/Camera uploads/UploadUtils/FileEncrypter.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/FileEncrypter.m b/iMEGA/Camera uploads/UploadUtils/FileEncrypter.m index c297f049bb..695e9906a0 100644 --- a/iMEGA/Camera uploads/UploadUtils/FileEncrypter.m +++ b/iMEGA/Camera uploads/UploadUtils/FileEncrypter.m @@ -1,4 +1,3 @@ - #import "FileEncrypter.h" #import "NSFileManager+MNZCategory.h" #import "NSError+CameraUpload.h" diff --git a/iMEGA/Camera uploads/UploadUtils/Heartbeat/BackupRegister.swift b/iMEGA/Camera uploads/UploadUtils/Heartbeat/BackupRegister.swift index 7c918cc756..3025ab23c0 100644 --- a/iMEGA/Camera uploads/UploadUtils/Heartbeat/BackupRegister.swift +++ b/iMEGA/Camera uploads/UploadUtils/Heartbeat/BackupRegister.swift @@ -1,6 +1,7 @@ import FirebaseCrashlytics import Foundation import MEGADomain +import MEGAL10n final class BackupRegister { diff --git a/iMEGA/Camera uploads/UploadUtils/Heartbeat/HeartbeatRequestDelegate.swift b/iMEGA/Camera uploads/UploadUtils/Heartbeat/HeartbeatRequestDelegate.swift index 90f4cd75ff..998c9148d9 100644 --- a/iMEGA/Camera uploads/UploadUtils/Heartbeat/HeartbeatRequestDelegate.swift +++ b/iMEGA/Camera uploads/UploadUtils/Heartbeat/HeartbeatRequestDelegate.swift @@ -9,7 +9,7 @@ extension MEGAError { } } -typealias HeartbeatRequestCompletion = (_ result: Result) -> Void +typealias HeartbeatRequestCompletion = (_ result: Result) -> Void final class HeartbeatRequestDelegate: NSObject, MEGARequestDelegate { private let completion: HeartbeatRequestCompletion diff --git a/iMEGA/Camera uploads/UploadUtils/ImageExporter.h b/iMEGA/Camera uploads/UploadUtils/ImageExporter.h index a515fab63c..9b74a1ffb3 100644 --- a/iMEGA/Camera uploads/UploadUtils/ImageExporter.h +++ b/iMEGA/Camera uploads/UploadUtils/ImageExporter.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/ImageExporter.m b/iMEGA/Camera uploads/UploadUtils/ImageExporter.m index 8b24827375..f62b66cca6 100644 --- a/iMEGA/Camera uploads/UploadUtils/ImageExporter.m +++ b/iMEGA/Camera uploads/UploadUtils/ImageExporter.m @@ -1,4 +1,3 @@ - #import "ImageExporter.h" #import "NSFileManager+MNZCategory.h" @import AVFoundation; diff --git a/iMEGA/Camera uploads/UploadUtils/LivePhotoScanner.h b/iMEGA/Camera uploads/UploadUtils/LivePhotoScanner.h index 0cef5ae649..9956eff477 100644 --- a/iMEGA/Camera uploads/UploadUtils/LivePhotoScanner.h +++ b/iMEGA/Camera uploads/UploadUtils/LivePhotoScanner.h @@ -1,4 +1,3 @@ - #import @import Photos; diff --git a/iMEGA/Camera uploads/UploadUtils/LivePhotoScanner.m b/iMEGA/Camera uploads/UploadUtils/LivePhotoScanner.m index da586591ec..b9cc2099e6 100644 --- a/iMEGA/Camera uploads/UploadUtils/LivePhotoScanner.m +++ b/iMEGA/Camera uploads/UploadUtils/LivePhotoScanner.m @@ -1,4 +1,3 @@ - #import "LivePhotoScanner.h" #import "CameraUploadRecordManager.h" #import "SavedIdentifierParser.h" diff --git a/iMEGA/Camera uploads/UploadUtils/LocalFileNameGenerator.h b/iMEGA/Camera uploads/UploadUtils/LocalFileNameGenerator.h index b2bbecf250..ce0fb950f7 100644 --- a/iMEGA/Camera uploads/UploadUtils/LocalFileNameGenerator.h +++ b/iMEGA/Camera uploads/UploadUtils/LocalFileNameGenerator.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/LocalFileNameGenerator.m b/iMEGA/Camera uploads/UploadUtils/LocalFileNameGenerator.m index 5b79adab24..e93b1b2e20 100644 --- a/iMEGA/Camera uploads/UploadUtils/LocalFileNameGenerator.m +++ b/iMEGA/Camera uploads/UploadUtils/LocalFileNameGenerator.m @@ -1,4 +1,3 @@ - #import "LocalFileNameGenerator.h" #import "NSString+MNZCategory.h" #import "MEGAStore.h" diff --git a/iMEGA/Camera uploads/UploadUtils/MediaInfoLoader.h b/iMEGA/Camera uploads/UploadUtils/MediaInfoLoader.h index 91c7f6f5cf..00f444228f 100644 --- a/iMEGA/Camera uploads/UploadUtils/MediaInfoLoader.h +++ b/iMEGA/Camera uploads/UploadUtils/MediaInfoLoader.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/MediaInfoLoader.m b/iMEGA/Camera uploads/UploadUtils/MediaInfoLoader.m index 144942365b..9762a665d4 100644 --- a/iMEGA/Camera uploads/UploadUtils/MediaInfoLoader.m +++ b/iMEGA/Camera uploads/UploadUtils/MediaInfoLoader.m @@ -1,4 +1,3 @@ - #import "MediaInfoLoader.h" #import "MEGASdkManager.h" #import "LoadMediaInfoOperation.h" diff --git a/iMEGA/Camera uploads/UploadUtils/SavedIdentifierParser.h b/iMEGA/Camera uploads/UploadUtils/SavedIdentifierParser.h index fe753aacfe..8f34e090a4 100644 --- a/iMEGA/Camera uploads/UploadUtils/SavedIdentifierParser.h +++ b/iMEGA/Camera uploads/UploadUtils/SavedIdentifierParser.h @@ -1,4 +1,3 @@ - #import #import "AssetIdentifierInfo.h" diff --git a/iMEGA/Camera uploads/UploadUtils/SavedIdentifierParser.m b/iMEGA/Camera uploads/UploadUtils/SavedIdentifierParser.m index 3850302184..6ed2c6149a 100644 --- a/iMEGA/Camera uploads/UploadUtils/SavedIdentifierParser.m +++ b/iMEGA/Camera uploads/UploadUtils/SavedIdentifierParser.m @@ -1,4 +1,3 @@ - #import "SavedIdentifierParser.h" static NSString * const MEGACameraUploadIdentifierSeparator = @","; diff --git a/iMEGA/Camera uploads/UploadUtils/UploadOperationFactory.h b/iMEGA/Camera uploads/UploadUtils/UploadOperationFactory.h index 991b053c33..8a8d800103 100644 --- a/iMEGA/Camera uploads/UploadUtils/UploadOperationFactory.h +++ b/iMEGA/Camera uploads/UploadUtils/UploadOperationFactory.h @@ -1,4 +1,3 @@ - #import #import "PhotoUploadOperation.h" #import "VideoUploadOperation.h" diff --git a/iMEGA/Camera uploads/UploadUtils/UploadOperationFactory.m b/iMEGA/Camera uploads/UploadUtils/UploadOperationFactory.m index b21b2394b1..68c76f3e43 100644 --- a/iMEGA/Camera uploads/UploadUtils/UploadOperationFactory.m +++ b/iMEGA/Camera uploads/UploadUtils/UploadOperationFactory.m @@ -1,4 +1,3 @@ - #import "UploadOperationFactory.h" #import "AssetUploadInfo.h" #import "LivePhotoUploadOperation.h" diff --git a/iMEGA/Camera uploads/UploadUtils/UploadRecordsCollator.h b/iMEGA/Camera uploads/UploadUtils/UploadRecordsCollator.h index 1f55be0675..08eae4e5ea 100644 --- a/iMEGA/Camera uploads/UploadUtils/UploadRecordsCollator.h +++ b/iMEGA/Camera uploads/UploadUtils/UploadRecordsCollator.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Camera uploads/UploadUtils/UploadRecordsCollator.m b/iMEGA/Camera uploads/UploadUtils/UploadRecordsCollator.m index faf03131bf..c2336a68a8 100644 --- a/iMEGA/Camera uploads/UploadUtils/UploadRecordsCollator.m +++ b/iMEGA/Camera uploads/UploadUtils/UploadRecordsCollator.m @@ -1,4 +1,3 @@ - #import "UploadRecordsCollator.h" #import "CameraUploadRecordManager.h" #import "NSURL+CameraUpload.h" diff --git a/iMEGA/Camera uploads/ViewModel/PhotoLibraryFilterViewModel.swift b/iMEGA/Camera uploads/ViewModel/PhotoLibraryFilterViewModel.swift index b0cc31a455..3d23fe33ee 100644 --- a/iMEGA/Camera uploads/ViewModel/PhotoLibraryFilterViewModel.swift +++ b/iMEGA/Camera uploads/ViewModel/PhotoLibraryFilterViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import MEGAPresentation final class PhotoLibraryFilterViewModel: ObservableObject { diff --git a/iMEGA/Chat Messages/AddToChatMenuViewController.swift b/iMEGA/Chat Messages/AddToChatMenuViewController.swift index 2c8faa3662..907bbf875e 100644 --- a/iMEGA/Chat Messages/AddToChatMenuViewController.swift +++ b/iMEGA/Chat Messages/AddToChatMenuViewController.swift @@ -1,4 +1,3 @@ - import UIKit protocol AddToChatMenuViewControllerDelegate: AnyObject { diff --git a/iMEGA/Chat Messages/AddToChatViewAnimator.swift b/iMEGA/Chat Messages/AddToChatViewAnimator.swift index 8af97796bc..59153a34cc 100644 --- a/iMEGA/Chat Messages/AddToChatViewAnimator.swift +++ b/iMEGA/Chat Messages/AddToChatViewAnimator.swift @@ -1,4 +1,3 @@ - // This class is responsible for animating the "AddToChatViewController" when presented. class AddToChatViewAnimator: NSObject, UIViewControllerAnimatedTransitioning { diff --git a/iMEGA/Chat Messages/BasicAudioController.swift b/iMEGA/Chat Messages/BasicAudioController.swift index dea40a52d1..58d1cab1c9 100644 --- a/iMEGA/Chat Messages/BasicAudioController.swift +++ b/iMEGA/Chat Messages/BasicAudioController.swift @@ -27,7 +27,7 @@ open class BasicAudioController: NSObject, AVAudioPlayerDelegate { open weak var playingCell: AudioMessageCell? /// The `MessageType` that is currently playing sound - open var playingMessage: MessageType? + open var playingMessage: (any MessageType)? /// Specify if current audio controller state: playing, in pause or none open private(set) var state: PlayerState = .stopped @@ -66,7 +66,7 @@ open class BasicAudioController: NSObject, AVAudioPlayerDelegate { @objc func proximityChanged() { DispatchQueue.main.async { - guard !AVAudioSession.sharedInstance().mnz_isBluetoothAudioRouteAvailable else { + guard !AVAudioSession.sharedInstance().isBluetoothAudioRouteAvailable else { return } let audioSessionUC = AudioSessionUseCase.default @@ -91,7 +91,7 @@ open class BasicAudioController: NSObject, AVAudioPlayerDelegate { /// /// - Note: /// This protocol method is called by MessageKit every time an audio cell needs to be configure - open func configureAudioCell(_ cell: AudioMessageCell, message: MessageType) { + open func configureAudioCell(_ cell: AudioMessageCell, message: any MessageType) { if isPlayingSameMessage(message), let collectionView = messageCollectionView, @@ -113,7 +113,7 @@ open class BasicAudioController: NSObject, AVAudioPlayerDelegate { /// - Parameters: /// - message: The `MessageType` that contain the audio item to be played. /// - audioCell: The `AudioMessageCell` that needs to be updated while audio is playing. - open func playSound(for message: MessageType, in audioCell: AudioMessageCell) { + open func playSound(for message: any MessageType, in audioCell: AudioMessageCell) { if AudioPlayerManager.shared.isPlayerAlive() { AudioPlayerManager.shared.audioInterruptionDidStart() @@ -164,7 +164,7 @@ open class BasicAudioController: NSObject, AVAudioPlayerDelegate { /// - Parameters: /// - message: The `MessageType` that contain the audio item to be pause. /// - audioCell: The `AudioMessageCell` that needs to be updated by the pause action. - open func pauseSound(for message: MessageType, in audioCell: AudioMessageCell) { + open func pauseSound(for message: any MessageType, in audioCell: AudioMessageCell) { guard let audioCell = audioCell as? ChatVoiceClipCollectionViewCell else { return } @@ -179,7 +179,7 @@ open class BasicAudioController: NSObject, AVAudioPlayerDelegate { } if AudioPlayerManager.shared.isPlayerAlive() { - let activeCall = MEGASdkManager.sharedMEGAChatSdk().mnz_existsActiveCall + let activeCall = MEGAChatSdk.shared.mnz_existsActiveCall AudioPlayerManager.shared.audioInterruptionDidEndNeedToResume(!activeCall) } } @@ -211,7 +211,7 @@ open class BasicAudioController: NSObject, AVAudioPlayerDelegate { setProximitySensorEnabled(false) if AudioPlayerManager.shared.isPlayerAlive() { - let activeCall = MEGASdkManager.sharedMEGAChatSdk().mnz_existsActiveCall + let activeCall = MEGAChatSdk.shared.mnz_existsActiveCall AudioPlayerManager.shared.audioInterruptionDidEndNeedToResume(!activeCall) } } @@ -261,7 +261,7 @@ open class BasicAudioController: NSObject, AVAudioPlayerDelegate { } } - func isPlayingSameMessage(_ message: MessageType) -> Bool { + func isPlayingSameMessage(_ message: any MessageType) -> Bool { if let playingMessage = playingMessage as? ChatMessage, let currentMessage = message as? ChatMessage, (playingMessage.messageId == currentMessage.messageId || currentMessage.message.nodeList?.node(at: 0)?.name == playingMessage.transfer?.fileName) { @@ -283,7 +283,7 @@ open class BasicAudioController: NSObject, AVAudioPlayerDelegate { stopAnyOngoingPlaying() } - open func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) { + open func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: (any Error)?) { stopAnyOngoingPlaying() } diff --git a/iMEGA/Chat Messages/ChatMessage.swift b/iMEGA/Chat Messages/ChatMessage.swift index 9c318ff5af..acb9e6a9fb 100644 --- a/iMEGA/Chat Messages/ChatMessage.swift +++ b/iMEGA/Chat Messages/ChatMessage.swift @@ -23,7 +23,7 @@ struct ChatMessage { } extension ChatMessage: MessageType { - var sender: SenderType { + var sender: any SenderType { return self } @@ -68,14 +68,14 @@ extension ChatMessage: SenderType { } if transfer?.type == .upload { - return String(format: "%llu", MEGASdkManager.sharedMEGAChatSdk().myUserHandle) + return String(format: "%llu", MEGAChatSdk.shared.myUserHandle) } return String(format: "%llu", message.userHandle) } var displayName: String { - let userEmail = MEGASdkManager.sharedMEGAChatSdk().userEmailFromCache(byUserHandle: message.userHandle) ?? "" + let userEmail = MEGAChatSdk.shared.userEmailFromCache(byUserHandle: message.userHandle) ?? "" let userName = chatRoom.userDisplayName(forUserHandle: message.userHandle) ?? userEmail return userName } diff --git a/iMEGA/Chat Messages/ChatMessageActionMenuViewController.swift b/iMEGA/Chat Messages/ChatMessageActionMenuViewController.swift index 57e62912a7..5f53a9f234 100644 --- a/iMEGA/Chat Messages/ChatMessageActionMenuViewController.swift +++ b/iMEGA/Chat Messages/ChatMessageActionMenuViewController.swift @@ -1,4 +1,5 @@ import Haptica +import MEGAL10n import UIKit class ChatMessageActionMenuViewController: ActionSheetViewController { @@ -254,11 +255,11 @@ class ChatMessageActionMenuViewController: ActionSheetViewController { actions = [forwardAction, exportMessagesAction, selectAction] if chatMessage.message.usersCount == 1 { - if let email = chatMessage.message.userEmail(at: 0), let user = MEGASdkManager.sharedMEGASdk().contact(forEmail: email), user.visibility != .visible { + if let email = chatMessage.message.userEmail(at: 0), let user = MEGASdk.shared.contact(forEmail: email), user.visibility != .visible { actions.append(contentsOf: [addContactAction]) } else { for index in 0.. Bool { - return UInt64(message.sender.senderId) == MEGASdkManager.sharedMEGAChatSdk().myUserHandle + return UInt64(message.sender.senderId) == MEGAChatSdk.shared.myUserHandle } } diff --git a/iMEGA/Chat Messages/ChatNotificationMessage.swift b/iMEGA/Chat Messages/ChatNotificationMessage.swift index de36028d6b..4522664abc 100644 --- a/iMEGA/Chat Messages/ChatNotificationMessage.swift +++ b/iMEGA/Chat Messages/ChatNotificationMessage.swift @@ -23,7 +23,7 @@ extension ChatNotificationMessage: SenderType { } extension ChatNotificationMessage: MessageType { - var sender: SenderType { + var sender: any SenderType { return self } diff --git a/iMEGA/Chat Messages/ChatRoomDelegate.swift b/iMEGA/Chat Messages/ChatRoomDelegate.swift index e286d80710..2b7385f988 100644 --- a/iMEGA/Chat Messages/ChatRoomDelegate.swift +++ b/iMEGA/Chat Messages/ChatRoomDelegate.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n import MessageKit class ChatRoomDelegate: NSObject, MEGAChatRoomDelegate, MEGAChatRequestDelegate { @@ -8,8 +9,8 @@ class ChatRoomDelegate: NSObject, MEGAChatRoomDelegate, MEGAChatRequestDelegate var transfers: [ChatMessage] = [] var chatRoom: MEGAChatRoom weak var chatViewController: ChatViewController? - var chatMessages: [MessageType] = [] - var messages: [MessageType] { + var chatMessages: [any MessageType] = [] + var messages: [any MessageType] { return chatMessages + transfers } @@ -733,7 +734,7 @@ extension ChatRoomDelegate: MEGATransferDelegate { } else if appData.contains("downloadAttachToMessageID") { let messageID = transfer.mnz_extractMessageIDFromAppData() - chatMessages = chatMessages.map({ (chatMessage) -> MessageType in + chatMessages = chatMessages.map({ (chatMessage) -> any MessageType in if var chatMessage = chatMessage as? ChatMessage, chatMessage.messageId == messageID { chatMessage.transfer = transfer @@ -766,7 +767,7 @@ extension ChatRoomDelegate: MEGATransferDelegate { } else if appData.contains("downloadAttachToMessageID") { let messageID = transfer.mnz_extractMessageIDFromAppData() - chatMessages = chatMessages.map({ (chatMessage) -> MessageType in + chatMessages = chatMessages.map({ (chatMessage) -> any MessageType in if var chatMessage = chatMessage as? ChatMessage, chatMessage.messageId == messageID { chatMessage.transfer = transfer diff --git a/iMEGA/Chat Messages/ChatViewAttachmentCellViewModel.swift b/iMEGA/Chat Messages/ChatViewAttachmentCellViewModel.swift index 8040b2d9d3..6ba8e2006e 100644 --- a/iMEGA/Chat Messages/ChatViewAttachmentCellViewModel.swift +++ b/iMEGA/Chat Messages/ChatViewAttachmentCellViewModel.swift @@ -1,3 +1,4 @@ +import MEGAL10n class ChatViewAttachmentCellViewModel { // MARK: - Private properties. diff --git a/iMEGA/Chat Messages/ChatViewController+CallStatus.swift b/iMEGA/Chat Messages/ChatViewController+CallStatus.swift index 6e3fad8ea6..75b3aaa8f7 100644 --- a/iMEGA/Chat Messages/ChatViewController+CallStatus.swift +++ b/iMEGA/Chat Messages/ChatViewController+CallStatus.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import MEGASDKRepo import UIKit @@ -108,26 +109,12 @@ extension ChatViewController { guard let self else { return } if granted { timer?.invalidate() - if shouldOpenWaitingRoom() { - openWaitingRoom() - } else { - openCallViewWithVideo(videoCall: false, shouldRing: false) - } + openCallViewWithVideo(videoCall: false, shouldRing: false) } else { permissionRouter.alertAudioPermission(incomingCall: false) } } } - - private func shouldOpenWaitingRoom() -> Bool { - let isModerator = chatRoom.ownPrivilege.toOwnPrivilegeEntity() == .moderator - return !isModerator && chatRoom.isWaitingRoomEnabled && chatContentViewModel.isWaitingRoomFeatureEnabled - } - - private func openWaitingRoom() { - guard let scheduledMeeting = scheduledMeetingUseCase.scheduledMeetingsByChat(chatId: chatRoom.chatId).first else { return } - WaitingRoomViewRouter(presenter: self, scheduledMeeting: scheduledMeeting).start() - } func subscribeToNoUserJoinedNotification() { let usecase = MeetingNoUserJoinedUseCase(repository: MeetingNoUserJoinedRepository.sharedRepo) @@ -149,11 +136,11 @@ extension ChatViewController { func showCallEndTimerIfNeeded(call: CallEntity) { guard MeetingContainerRouter.isAlreadyPresented == false, - call.changeTye == .callComposition, + call.changeType == .callComposition, call.numberOfParticipants == 1, call.participants.first == chatContentViewModel.userHandle else { - if call.changeTye == .callComposition, call.numberOfParticipants > 1 { + if call.changeType == .callComposition, call.numberOfParticipants > 1 { removeEndCallDialogIfNeeded() cancelEndCallSubscription() } diff --git a/iMEGA/Chat Messages/ChatViewController+ChatDelegate.swift b/iMEGA/Chat Messages/ChatViewController+ChatDelegate.swift index 234369632f..ea2f3ee0d4 100644 --- a/iMEGA/Chat Messages/ChatViewController+ChatDelegate.swift +++ b/iMEGA/Chat Messages/ChatViewController+ChatDelegate.swift @@ -1,4 +1,3 @@ - extension ChatViewController: MEGAChatDelegate { func onChatConnectionStateUpdate(_ api: MEGAChatSdk, chatId: UInt64, newState: Int32) { if chatRoom.chatId == chatId { diff --git a/iMEGA/Chat Messages/ChatViewController+Docscan.swift b/iMEGA/Chat Messages/ChatViewController+Docscan.swift index 9780c3b4cc..03ad088290 100644 --- a/iMEGA/Chat Messages/ChatViewController+Docscan.swift +++ b/iMEGA/Chat Messages/ChatViewController+Docscan.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n import VisionKit extension ChatViewController: VNDocumentCameraViewControllerDelegate { @@ -18,7 +19,7 @@ extension ChatViewController: VNDocumentCameraViewControllerDelegate { vc.chatRoom = self.chatRoom let nav = MEGANavigationController(rootViewController: vc) nav.modalPresentationStyle = .fullScreen - nav.addLeftDismissButton(withText: NSLocalizedString("cancel", comment: "")) + nav.addLeftDismissButton(withText: Strings.localized("cancel", comment: "")) self.present(viewController: nav) } } diff --git a/iMEGA/Chat Messages/ChatViewController+InputBar.swift b/iMEGA/Chat Messages/ChatViewController+InputBar.swift index e6368dbda5..f1a2947566 100644 --- a/iMEGA/Chat Messages/ChatViewController+InputBar.swift +++ b/iMEGA/Chat Messages/ChatViewController+InputBar.swift @@ -1,6 +1,9 @@ +import ChatRepo import CoreServices import Foundation import ISEmojiView +import MEGAL10n +import MEGASDKRepo import MessageKit import VisionKit @@ -127,14 +130,15 @@ extension ChatViewController { // MARK: - Private methods. private func join(button: UIButton) { - if MEGASdkManager.sharedMEGAChatSdk().initState() == .anonymous { + if MEGAChatSdk.shared.initState() == .anonymous { MEGALinkManager.secondaryLinkURL = publicChatLink MEGALinkManager.selectedOption = .joinChatLink dismissChatRoom() } else { - let delegate = MEGAChatGenericRequestDelegate { (request, _) in - guard let chatRoom = MEGASdkManager.sharedMEGAChatSdk().chatRoom(forChatId: request.chatHandle) else { - MEGALogDebug("ChatRoom not found with chat handle \(request.chatHandle)") + let delegate = ChatRequestDelegate { result in + guard case let .success(request) = result, + let chatRoom = MEGAChatSdk.shared.chatRoom(forChatId: request.chatHandle) else { + MEGALogDebug("ChatRoom not found with chat handle") return } let chatViewController = ChatViewController(chatRoom: chatRoom) @@ -145,7 +149,7 @@ extension ChatViewController { self.updateJoinView() } - MEGASdkManager.sharedMEGAChatSdk().autojoinPublicChat(chatRoom.chatId, delegate: delegate) + MEGAChatSdk.shared.autojoinPublicChat(chatRoom.chatId, delegate: delegate) if let handle = MEGASdk.base64Handle(forUserHandle: chatRoom.chatId) { MEGALinkManager.joiningOrLeavingChatBase64Handles.add(handle) } @@ -216,7 +220,7 @@ extension ChatViewController { selectedUsers.forEach { peerlist.addPeer(withHandle: $0.handle, privilege: 2)} if keyRotationEnabled { - MEGASdkManager.sharedMEGAChatSdk().mnz_createChatRoom( + MEGAChatSdk.shared.mnz_createChatRoom( usersArray: selectedUsers, title: groupName, allowNonHostToAddParticipants: allowNonHostToAddParticipants @@ -226,14 +230,15 @@ extension ChatViewController { } } } else { - let createChatGroupRequestDelegate = MEGAChatGenericRequestDelegate { request, error in - guard let newChatRoom = MEGASdkManager.sharedMEGAChatSdk().chatRoom(forChatId: request.chatHandle) else { - MEGALogDebug("Cannot find chatRoom chat id \(request.chatHandle)") + let createChatGroupRequestDelegate = ChatRequestDelegate { result in + guard case let .success(request) = result, + let newChatRoom = MEGAChatSdk.shared.chatRoom(forChatId: request.chatHandle) else { + MEGALogDebug("Cannot find chatRoom") return } if getChatLink { - let genericRequestDelegate = MEGAChatGenericRequestDelegate { (request, error) in - if error.type == .MEGAChatErrorTypeOk { + let genericRequestDelegate = ChatRequestDelegate { result in + if case let .success(request) = result { let chatViewController = ChatViewController(chatRoom: newChatRoom) chatViewController.publicChatWithLinkCreated = true chatViewController.publicChatLink = URL(string: request.text) @@ -243,14 +248,14 @@ extension ChatViewController { } } - MEGASdkManager.sharedMEGAChatSdk().createChatLink(newChatRoom.chatId, delegate: genericRequestDelegate) + MEGAChatSdk.shared.createChatLink(newChatRoom.chatId, delegate: genericRequestDelegate) } else { DispatchQueue.main.async { self.open(chatRoom: newChatRoom) } } } - MEGASdkManager.sharedMEGAChatSdk().createPublicChat(withPeers: peerlist, + MEGAChatSdk.shared.createPublicChat(withPeers: peerlist, title: groupName, speakRequest: false, waitingRoom: false, @@ -425,13 +430,13 @@ extension ChatViewController: ChatInputBarDelegate { } func tappedSendButton(withText text: String) { - MEGASdkManager.sharedMEGAChatSdk().sendStopTypingNotification(forChat: chatRoom.chatId) + MEGAChatSdk.shared.sendStopTypingNotification(forChat: chatRoom.chatId) if let editMessage = editMessage { let messageId = (editMessage.message.status == .sending) ? editMessage.message.temporalId : editMessage.message.messageId if editMessage.message.content != text, - let message = MEGASdkManager.sharedMEGAChatSdk().editMessage(forChat: chatRoom.chatId, messageId: messageId, message: text) { + let message = MEGAChatSdk.shared.editMessage(forChat: chatRoom.chatId, messageId: messageId, message: text) { message.chatId = chatRoom.chatId let firstIndex = messages.firstIndex { message -> Bool in @@ -452,7 +457,7 @@ extension ChatViewController: ChatInputBarDelegate { } self.editMessage = nil - } else if let message = MEGASdkManager.sharedMEGAChatSdk().sendMessage(toChat: chatRoom.chatId, message: text) { + } else if let message = MEGAChatSdk.shared.sendMessage(toChat: chatRoom.chatId, message: text) { chatRoomDelegate.updateUnreadMessagesLabel(unreads: 0) chatRoomDelegate.insertMessage(message, scrollToBottom: true) checkDialogs(message) @@ -465,7 +470,7 @@ extension ChatViewController: ChatInputBarDelegate { func checkDialogs(_ message: MEGAChatMessage) { if let content = message.content, MEGAChatSdk.hasUrl(content) { - MEGASdkManager.sharedMEGASdk().shouldShowRichLinkWarning(with: MEGAGetAttrUserRequestDelegate(completion: { (request) in + MEGASdk.shared.shouldShowRichLinkWarning(with: MEGAGetAttrUserRequestDelegate(completion: { (request) in if let request = request, request.flag { message.warningDialog = (request.number.intValue >= 3 ? MEGAChatMessageWarningDialog.standard : MEGAChatMessageWarningDialog.initial) self.richLinkWarningCounterValue = request.number.uintValue @@ -494,7 +499,7 @@ extension ChatViewController: ChatInputBarDelegate { let appData = ("" as NSString).mnz_appDataToAttach(toChatID: self.chatRoom.chatId, asVoiceClip: true) - if let voiceMessagesNode = MEGASdkManager.sharedMEGASdk().node(forPath: MEGAVoiceMessagesFolderName, node: myChatFilesFolderNode) { + if let voiceMessagesNode = MEGASdk.shared.node(forPath: MEGAVoiceMessagesFolderName, node: myChatFilesFolderNode) { ChatUploader.sharedInstance.upload(filepath: path, appData: appData, chatRoomId: self.chatRoom.chatId, @@ -507,7 +512,7 @@ extension ChatViewController: ChatInputBarDelegate { fatalError("request object should not be nil") } - if let voiceMessagesNode = MEGASdkManager.sharedMEGASdk().node(forHandle: request.nodeHandle) { + if let voiceMessagesNode = MEGASdk.shared.node(forHandle: request.nodeHandle) { ChatUploader.sharedInstance.upload(filepath: path, appData: appData, chatRoomId: self.chatRoom.chatId, @@ -519,7 +524,7 @@ extension ChatViewController: ChatInputBarDelegate { } } - MEGASdkManager.sharedMEGASdk().createFolder(withName: MEGAVoiceMessagesFolderName, + MEGASdk.shared.createFolder(withName: MEGAVoiceMessagesFolderName, parent: myChatFilesFolderNode, delegate: requestDelegate) } @@ -544,7 +549,7 @@ extension ChatViewController: ChatInputBarDelegate { func typing(withText text: String) { if text.isEmpty { - MEGASdkManager.sharedMEGAChatSdk().sendStopTypingNotification(forChat: chatRoom.chatId) + MEGAChatSdk.shared.sendStopTypingNotification(forChat: chatRoom.chatId) if sendTypingTimer != nil { self.sendTypingTimer?.invalidate() self.sendTypingTimer = nil @@ -559,7 +564,7 @@ extension ChatViewController: ChatInputBarDelegate { timer.invalidate() self.sendTypingTimer = nil } - MEGASdkManager.sharedMEGAChatSdk().sendTypingNotification(forChat: chatRoom.chatId) + MEGAChatSdk.shared.sendTypingNotification(forChat: chatRoom.chatId) } } @@ -688,7 +693,7 @@ extension ChatViewController: AddToChatViewControllerDelegate { selectedNodes.forEach { node in Helper.import(node) { newNode in - MEGASdkManager.sharedMEGAChatSdk().attachNode(toChat: self.chatRoom.chatId, node: newNode.handle) + MEGAChatSdk.shared.attachNode(toChat: self.chatRoom.chatId, node: newNode.handle) } } } @@ -713,7 +718,7 @@ extension ChatViewController: AddToChatViewControllerDelegate { return } - if let message = MEGASdkManager.sharedMEGAChatSdk().attachContacts(toChat: self.chatRoom.chatId, + if let message = MEGAChatSdk.shared.attachContacts(toChat: self.chatRoom.chatId, contacts: users) { self.chatRoomDelegate.insertMessage(message) } @@ -723,8 +728,8 @@ extension ChatViewController: AddToChatViewControllerDelegate { } func showLocation() { - let genericRequestDelegate = MEGAGenericRequestDelegate { (_, error) in - if error.type != .apiOk { + let genericRequestDelegate = RequestDelegate { result in + if case .success = result { let title = Strings.Localizable.sendLocation let message = Strings.Localizable.thisLocationWillBeOpenedUsingAThirdPartyMapsProviderOutsideTheEndToEndEncryptedMEGAPlatform @@ -732,8 +737,8 @@ extension ChatViewController: AddToChatViewControllerDelegate { let cancelAction = UIAlertAction(title: Strings.Localizable.cancel, style: .cancel, handler: nil) let continueAction = UIAlertAction(title: Strings.Localizable.continue, style: .default) { _ in - let enableGeolocationDelegate = MEGAGenericRequestDelegate { (_, error) in - if error.type != .apiOk { + let enableGeolocationDelegate = RequestDelegate { result in + if case let .failure(error) = result { let alertTitle = Strings.Localizable.error let errorName = error.name ?? Strings.Localizable.somethingWentWrong let alertMessage = Strings.Localizable.Chat.Map.Location.enableGeolocationFailedError(errorName) @@ -750,7 +755,7 @@ extension ChatViewController: AddToChatViewControllerDelegate { self.presentShareLocation() } } - MEGASdkManager.sharedMEGASdk().enableGeolocation(with: enableGeolocationDelegate) + MEGASdk.shared.enableGeolocation(with: enableGeolocationDelegate) } let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) @@ -761,7 +766,7 @@ extension ChatViewController: AddToChatViewControllerDelegate { self.presentShareLocation() } } - MEGASdkManager.sharedMEGASdk().isGeolocationEnabled(with: genericRequestDelegate) + MEGASdk.shared.isGeolocationEnabled(with: genericRequestDelegate) } var canRecordAudio: Bool { diff --git a/iMEGA/Chat Messages/ChatViewController+Menus.swift b/iMEGA/Chat Messages/ChatViewController+Menus.swift index f7b4acceaf..fa52c0ad14 100644 --- a/iMEGA/Chat Messages/ChatViewController+Menus.swift +++ b/iMEGA/Chat Messages/ChatViewController+Menus.swift @@ -1,6 +1,6 @@ - import Foundation import MEGADomain +import MEGAL10n import MEGASDKRepo extension ChatViewController { @@ -55,7 +55,7 @@ extension ChatViewController { audioController.stopAnyOngoingPlaying() } } - MEGASdkManager.sharedMEGAChatSdk().revokeAttachmentMessage(forChat: chatRoom.chatId, messageId: megaMessage.messageId) + MEGAChatSdk.shared.revokeAttachmentMessage(forChat: chatRoom.chatId, messageId: megaMessage.messageId) } else { let foundIndex = messages.firstIndex { message -> Bool in guard let localChatMessage = message as? ChatMessage else { @@ -76,7 +76,7 @@ extension ChatViewController { }, completion: nil) } else { let messageId = megaMessage.status == .sending ? megaMessage.temporalId : megaMessage.messageId - let deleteMessage = MEGASdkManager.sharedMEGAChatSdk().deleteMessage(forChat: chatRoom.chatId, messageId: messageId) + let deleteMessage = MEGAChatSdk.shared.deleteMessage(forChat: chatRoom.chatId, messageId: messageId) deleteMessage?.chatId = chatRoom.chatId chatRoomDelegate.chatMessages[index] = ChatMessage(message: deleteMessage!, chatRoom: chatRoom) } @@ -86,7 +86,7 @@ extension ChatViewController { func removeRichPreview(_ message: ChatMessage) { let megaMessage = message.message - MEGASdkManager.sharedMEGAChatSdk().removeRichLink(forChat: chatRoom.chatId, messageId: megaMessage.messageId) + MEGAChatSdk.shared.removeRichLink(forChat: chatRoom.chatId, messageId: megaMessage.messageId) } func downloadMessage(_ messages: [ChatMessage]) { @@ -161,7 +161,7 @@ extension ChatViewController { node = authorizedNode } - let saveMediaUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: MEGASdkManager.sharedMEGASdk()), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) + let saveMediaUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: .shared), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) TransfersWidgetViewController.sharedTransfer().setProgressViewInKeyWindow() TransfersWidgetViewController.sharedTransfer().progressView?.showWidgetIfNeeded() TransfersWidgetViewController.sharedTransfer().bringProgressToFrontKeyWindowIfNeeded() diff --git a/iMEGA/Chat Messages/ChatViewController+MessageCellDelegate.swift b/iMEGA/Chat Messages/ChatViewController+MessageCellDelegate.swift index 9e4279fac6..f8178ef451 100644 --- a/iMEGA/Chat Messages/ChatViewController+MessageCellDelegate.swift +++ b/iMEGA/Chat Messages/ChatViewController+MessageCellDelegate.swift @@ -1,5 +1,6 @@ import MapKit import MEGADomain +import MEGAL10n import MessageKit extension ChatViewController: MessageCellDelegate, MessageLabelDelegate { @@ -28,13 +29,13 @@ extension ChatViewController: MessageCellDelegate, MessageLabelDelegate { } private func showAvatarActions(for chatMessage: ChatMessage, cell: MessageContentCell) { - guard let userEmail = MEGASdkManager.sharedMEGAChatSdk().userEmailFromCache(byUserHandle: chatMessage.message.userHandle) else { + guard let userEmail = MEGAChatSdk.shared.userEmailFromCache(byUserHandle: chatMessage.message.userHandle) else { return } var actions = [createInfoAction(for: chatMessage, userEmail: userEmail)] - let user = MEGASdkManager.sharedMEGASdk().contact(forEmail: userEmail) + let user = MEGASdk.shared.contact(forEmail: userEmail) if user == nil || user?.visibility != MEGAUserVisibility.visible { actions.append(createAddContactsAction(forEmail: userEmail)) } @@ -69,14 +70,14 @@ extension ChatViewController: MessageCellDelegate, MessageLabelDelegate { private func createAddContactsAction(forEmail userEmail: String) -> ActionSheetAction { ActionSheetAction(title: Strings.Localizable.addContact, detail: nil, image: nil, style: .default) { if MEGAReachabilityManager.isReachableHUDIfNot() { - MEGASdkManager.sharedMEGASdk().inviteContact(withEmail: userEmail, message: "", action: .add, delegate: MEGAInviteContactRequestDelegate(numberOfRequests: 1)) + MEGASdk.shared.inviteContact(withEmail: userEmail, message: "", action: .add, delegate: MEGAInviteContactRequestDelegate(numberOfRequests: 1)) } } } private func createRemoveParticipantAction(for chatMessage: ChatMessage) -> ActionSheetAction { ActionSheetAction(title: Strings.Localizable.removeParticipant, detail: nil, image: nil, style: .default) { - MEGASdkManager.sharedMEGAChatSdk().remove(fromChat: chatMessage.chatRoom.chatId, userHandle: chatMessage.message.userHandle) + MEGAChatSdk.shared.remove(fromChat: chatMessage.chatRoom.chatId, userHandle: chatMessage.message.userHandle) } } @@ -132,7 +133,7 @@ extension ChatViewController: MessageCellDelegate, MessageLabelDelegate { let transfer = chatMessage.transfer, transfer.state == .failed else { return } - MEGASdkManager.sharedMEGASdk().retryTransfer(transfer) + MEGASdk.shared.retryTransfer(transfer) messagesCollectionView.reloadData() } @@ -194,7 +195,7 @@ extension ChatViewController: MessageCellDelegate, MessageLabelDelegate { if megaMessage.nodeList?.size.uintValue == 1 { var node = megaMessage.nodeList?.node(at: 0) if chatRoom.isPreview { - node = MEGASdkManager.sharedMEGASdk().authorizeChatNode(node!, cauth: chatRoom.authorizationToken) + node = MEGASdk.shared.authorizeChatNode(node!, cauth: chatRoom.authorizationToken) } if let name = node?.name, @@ -212,7 +213,7 @@ extension ChatViewController: MessageCellDelegate, MessageLabelDelegate { } if chatRoom.isPreview { - if let authorizedNode = MEGASdkManager.sharedMEGASdk().authorizeChatNode(node, cauth: chatRoom.authorizationToken) { + if let authorizedNode = MEGASdk.shared.authorizeChatNode(node, cauth: chatRoom.authorizationToken) { if localChatMessage == chatMessage { foundIndex = mediaNodesArrayIndex } @@ -233,7 +234,7 @@ extension ChatViewController: MessageCellDelegate, MessageLabelDelegate { } let photoBrowserVC = MEGAPhotoBrowserViewController.photoBrowser(withMediaNodes: NSMutableArray(array: mediaNodesArray), - api: MEGASdkManager.sharedMEGASdk(), + api: MEGASdk.shared, displayMode: .chatAttachment, preferredIndex: UInt(foundIndex ?? 0)) photoBrowserVC.configureMediaAttachment(forMessageId: megaMessage.messageId, inChatId: chatRoom.chatId, messagesIds: mediaMessagesArray) diff --git a/iMEGA/Chat Messages/ChatViewController+MessageLayoutDelegate.swift b/iMEGA/Chat Messages/ChatViewController+MessageLayoutDelegate.swift index 4622500de0..8d3f4902cc 100644 --- a/iMEGA/Chat Messages/ChatViewController+MessageLayoutDelegate.swift +++ b/iMEGA/Chat Messages/ChatViewController+MessageLayoutDelegate.swift @@ -17,7 +17,7 @@ extension ChatViewController: ChatViewMessagesLayoutDelegate { return !chatMessage.message.isManagementMessage } - func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { + func cellTopLabelHeight(for message: any MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { guard !(message is ChatNotificationMessage) && !indexPath.isEmpty else { return 0.0 } @@ -25,7 +25,7 @@ extension ChatViewController: ChatViewMessagesLayoutDelegate { return isDateLabelVisible(for: indexPath) ? 30.0 : 0.0 } - func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { + func messageTopLabelHeight(for message: any MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { if message is ChatNotificationMessage { return 0.0 } @@ -58,7 +58,7 @@ extension ChatViewController: ChatViewMessagesLayoutDelegate { guard let message = messages[section] as? ChatMessage else { return .zero } - let list = MEGASdkManager.sharedMEGAChatSdk().messageReactions(forChat: message.chatRoom.chatId, messageId: message.message.messageId) + let list = MEGAChatSdk.shared.messageReactions(forChat: message.chatRoom.chatId, messageId: message.message.messageId) if message.message.isManagementMessage || list?.size == 0 { return .zero @@ -70,7 +70,7 @@ extension ChatViewController: ChatViewMessagesLayoutDelegate { } - func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { + func messageBottomLabelHeight(for message: any MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat { guard let message = message as? ChatMessage, let transfer = message.transfer, transfer.state == .failed else { return 0 } diff --git a/iMEGA/Chat Messages/ChatViewController+MessagesDataSource.swift b/iMEGA/Chat Messages/ChatViewController+MessagesDataSource.swift index a8dfdb44af..0e69ffae44 100644 --- a/iMEGA/Chat Messages/ChatViewController+MessagesDataSource.swift +++ b/iMEGA/Chat Messages/ChatViewController+MessagesDataSource.swift @@ -1,8 +1,8 @@ +import MEGAL10n import MessageKit extension ChatViewController: MessagesDataSource { - - public func currentSender() -> SenderType { + public func currentSender() -> any SenderType { return myUser } @@ -11,11 +11,11 @@ extension ChatViewController: MessagesDataSource { } public func messageForItem(at indexPath: IndexPath, - in messagesCollectionView: MessagesCollectionView) -> MessageType { + in messagesCollectionView: MessagesCollectionView) -> any MessageType { return messages[safe: indexPath.section] ?? ConcreteMessageType(sender: User(senderId: "", displayName: ""), messageId: "", sentDate: Date(), kind: .text("")) } - func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { + func messageTopLabelAttributedText(for message: any MessageType, at indexPath: IndexPath) -> NSAttributedString? { guard let message = messages[safe: indexPath.section], let chatMessage = message as? ChatMessage else { return nil @@ -37,7 +37,7 @@ extension ChatViewController: MessagesDataSource { return nil } - func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { + func cellTopLabelAttributedText(for message: any MessageType, at indexPath: IndexPath) -> NSAttributedString? { if isDateLabelVisible(for: indexPath) { return NSAttributedString( string: NSCalendar.current.isDateInToday(message.sentDate) ? Strings.Localizable.today : message.sentDate.string(withDateFormat: "E dd MMM"), @@ -51,7 +51,7 @@ extension ChatViewController: MessagesDataSource { func messageHeaderView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView { - guard MEGASdkManager.sharedMEGAChatSdk().isFullHistoryLoaded(forChat: chatRoom.chatId) else { + guard MEGAChatSdk.shared.isFullHistoryLoaded(forChat: chatRoom.chatId) else { let loadingMessagesHeaderView = messagesCollectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: LoadingMessageReusableView.reuseIdentifier, for: indexPath) as! LoadingMessageReusableView loadingMessagesHeaderView.loadingView.mnz_startShimmering() return loadingMessagesHeaderView @@ -76,7 +76,7 @@ extension ChatViewController: MessagesDataSource { return chatMessageReactionView } - func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? { + func messageBottomLabelAttributedText(for message: any MessageType, at indexPath: IndexPath) -> NSAttributedString? { guard let message = message as? ChatMessage, let transfer = message.transfer, transfer.state == .failed else { return nil } @@ -122,9 +122,7 @@ extension ChatViewController: MessageReactionReusableViewDelegate { guard chatRoom.canAddReactions else { return } - guard let emojisStringList = MEGASdkManager - .sharedMEGAChatSdk() - .messageReactions(forChat: chatRoom.chatId, + guard let emojisStringList = MEGAChatSdk.shared.messageReactions(forChat: chatRoom.chatId, messageId: chatMessage.message.messageId) else { MEGALogDebug("Could not fetch the emoji list for a message") return diff --git a/iMEGA/Chat Messages/ChatViewController+MessagesDisplayDelegate.swift b/iMEGA/Chat Messages/ChatViewController+MessagesDisplayDelegate.swift index b7b10d4444..beaf3aaedb 100644 --- a/iMEGA/Chat Messages/ChatViewController+MessagesDisplayDelegate.swift +++ b/iMEGA/Chat Messages/ChatViewController+MessagesDisplayDelegate.swift @@ -2,7 +2,7 @@ import MessageKit extension ChatViewController: MessagesDisplayDelegate { - func backgroundColor(for message: MessageType, + func backgroundColor(for message: any MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { @@ -34,11 +34,11 @@ extension ChatViewController: MessagesDisplayDelegate { } - func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { + func textColor(for message: any MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { return isFromCurrentSender(message: message) ? .white : .mnz_label() } - func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { + func messageStyle(for message: any MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle { return .custom { [weak self] containerView in guard let `self` = self else { return @@ -86,7 +86,7 @@ extension ChatViewController: MessagesDisplayDelegate { } } - func configureAccessoryView(_ accessoryView: UIView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) { + func configureAccessoryView(_ accessoryView: UIView, for message: any MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) { // Cells are reused, so only add a button here once. For real use you would need to // ensure any subviews are removed if not needed accessoryView.subviews.forEach { $0.removeFromSuperview() } @@ -109,7 +109,7 @@ extension ChatViewController: MessagesDisplayDelegate { } func configureAvatarView(_ avatarView: AvatarView, - for message: MessageType, + for message: any MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) { @@ -131,7 +131,7 @@ extension ChatViewController: MessagesDisplayDelegate { } } - func detectorAttributes(for detector: DetectorType, and message: MessageType, at indexPath: IndexPath) -> [NSAttributedString.Key: Any] { + func detectorAttributes(for detector: DetectorType, and message: any MessageType, at indexPath: IndexPath) -> [NSAttributedString.Key: Any] { let color = textColor(for: message, at: indexPath, in: messagesCollectionView) return [.foregroundColor: color, .underlineStyle: NSUnderlineStyle.single.rawValue, @@ -140,17 +140,17 @@ extension ChatViewController: MessagesDisplayDelegate { } - func enabledDetectors(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> [DetectorType] { + func enabledDetectors(for message: any MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> [DetectorType] { return [.url, .address, .phoneNumber, .transitInformation, .mention, .hashtag] } // MARK: - Audio Messages - func audioTintColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { + func audioTintColor(for message: any MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor { return isFromCurrentSender(message: message) ? .white : .mnz_label() } - func configureAudioCell(_ cell: AudioMessageCell, message: MessageType) { + func configureAudioCell(_ cell: AudioMessageCell, message: any MessageType) { audioController.configureAudioCell(cell, message: message) // this is needed especily when the cell is reconfigure while is playing sound } @@ -160,7 +160,7 @@ extension ChatViewController: MessagesDisplayDelegate { // MARK: - Private methods - private func shouldShowAccessoryView(for message: MessageType) -> Bool { + private func shouldShowAccessoryView(for message: some MessageType) -> Bool { guard let chatMessage = message as? ChatMessage, !isEditing else { return false } diff --git a/iMEGA/Chat Messages/ChatViewController+NavigationBar.swift b/iMEGA/Chat Messages/ChatViewController+NavigationBar.swift index 2d772de5d6..d9907f48cc 100644 --- a/iMEGA/Chat Messages/ChatViewController+NavigationBar.swift +++ b/iMEGA/Chat Messages/ChatViewController+NavigationBar.swift @@ -1,4 +1,3 @@ - import Foundation import MEGADomain @@ -32,7 +31,7 @@ extension ChatViewController { navigationItem.rightBarButtonItems = rightBarButtons let reachable = MEGAReachabilityManager.isReachable() - let existsActiveCall = MEGASdkManager.sharedMEGAChatSdk().mnz_existsActiveCall + let existsActiveCall = MEGAChatSdk.shared.mnz_existsActiveCall chatContentViewModel.dispatch(.updateCallNavigationBarButtons(shouldDisableAudioVideoCalling, isVoiceRecordingInProgress, reachable, existsActiveCall)) @@ -100,7 +99,7 @@ extension ChatViewController { return } - guard let userEmail = MEGASdkManager.sharedMEGAChatSdk().userEmailFromCache(byUserHandle: peerHandle) else { + guard let userEmail = MEGAChatSdk.shared.userEmailFromCache(byUserHandle: peerHandle) else { return } @@ -130,7 +129,7 @@ extension ChatViewController { ) { [weak self] handles in guard let self = self else { return } for handle in handles { - MEGASdkManager.sharedMEGAChatSdk().invite( + MEGAChatSdk.shared.invite( toChat: self.chatRoom.chatId, user: handle, privilege: MEGAChatRoomPrivilege.standard.rawValue diff --git a/iMEGA/Chat Messages/ChatViewController+Toolbar.swift b/iMEGA/Chat Messages/ChatViewController+Toolbar.swift index f5f0378827..f70d89e8f2 100644 --- a/iMEGA/Chat Messages/ChatViewController+Toolbar.swift +++ b/iMEGA/Chat Messages/ChatViewController+Toolbar.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import UIKit enum ToolbarType { @@ -69,7 +70,7 @@ extension ChatViewController { audioController.stopAnyOngoingPlaying() } } - MEGASdkManager.sharedMEGAChatSdk().revokeAttachmentMessage(forChat: chatRoom.chatId, messageId: megaMessage.messageId) + MEGAChatSdk.shared.revokeAttachmentMessage(forChat: chatRoom.chatId, messageId: megaMessage.messageId) } else { let foundIndex = messages.firstIndex { message -> Bool in guard let localChatMessage = message as? ChatMessage else { @@ -90,7 +91,7 @@ extension ChatViewController { }, completion: nil) } else { let messageId = megaMessage.status == .sending ? megaMessage.temporalId : megaMessage.messageId - let deleteMessage = MEGASdkManager.sharedMEGAChatSdk().deleteMessage(forChat: chatRoom.chatId, messageId: messageId) + let deleteMessage = MEGAChatSdk.shared.deleteMessage(forChat: chatRoom.chatId, messageId: messageId) deleteMessage?.chatId = chatRoom.chatId chatRoomDelegate.chatMessages[index] = ChatMessage(message: deleteMessage!, chatRoom: chatRoom) } @@ -150,7 +151,7 @@ extension ChatViewController { } else if chatIdNumbers?.count == 1 && self.chatRoom.isPreview { guard let chatId = chatIdNumbers?.first?.uint64Value, - let chatRoom = MEGASdkManager.sharedMEGAChatSdk().chatRoom(forChatId: chatId) else { + let chatRoom = MEGAChatSdk.shared.chatRoom(forChatId: chatId) else { MEGALogDebug("Cannot find chatRoom chat") return } diff --git a/iMEGA/Chat Messages/ChatViewController.swift b/iMEGA/Chat Messages/ChatViewController.swift index 6b92e382ac..42e1f9443c 100644 --- a/iMEGA/Chat Messages/ChatViewController.swift +++ b/iMEGA/Chat Messages/ChatViewController.swift @@ -2,6 +2,7 @@ import ChatRepo import Combine import KeyboardLayoutGuide import MEGADomain +import MEGAL10n import MEGAPermissions import MEGASDKRepo import MEGAUIKit @@ -118,7 +119,7 @@ class ChatViewController: MessagesViewController { return view }() - var messages: [MessageType] { + var messages: [any MessageType] { return chatRoomDelegate.messages } @@ -483,7 +484,7 @@ class ChatViewController: MessagesViewController { return super.collectionView(collectionView, viewForSupplementaryElementOfKind: kind, at: indexPath) } - func customCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell { + func customCell(for message: any MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell { guard let messagesDataSource = messagesCollectionView.messagesDataSource else { fatalError("Ouch. nil data source for messages") @@ -692,7 +693,7 @@ class ChatViewController: MessagesViewController { // MARK: - Internal methods used by the extension of this class - func isFromCurrentSender(message: MessageType) -> Bool { + func isFromCurrentSender(message: any MessageType) -> Bool { return UInt64(message.sender.senderId) == MEGAChatSdk.shared.myUserHandle } @@ -756,14 +757,14 @@ class ChatViewController: MessagesViewController { return currentSenderId == previousSenderId } - func avatarImage(for message: MessageType) -> UIImage? { + func avatarImage(for message: any MessageType) -> UIImage? { guard let userHandle = UInt64(message.sender.senderId) else { return nil } return UIImage.mnz_image(forUserHandle: userHandle, name: message.sender.displayName, size: CGSize(width: 24, height: 24), delegate: RequestDelegate(completion: { _ in })) } - func initials(for message: MessageType) -> String { + func initials(for message: any MessageType) -> String { guard let userHandle = UInt64(message.sender.senderId) else { return "" } @@ -801,6 +802,8 @@ class ChatViewController: MessagesViewController { shouldEnableAudioVideoButtons(enable) case .startMeetingNoRinging(let videoCall, let scheduledMeeting): startMeetingNoRinging(videoCall: videoCall, scheduledMeeting: scheduledMeeting) + case .startMeetingInWaitingRoomChat(let videoCall, let scheduledMeeting): + startMeetingInWaitingRoomChat(videoCall: videoCall, scheduledMeeting: scheduledMeeting) case .startOutGoingCall(let videoEnable): startOutGoingCall(isVideoEnabled: videoEnable) } @@ -1147,14 +1150,29 @@ class ChatViewController: MessagesViewController { } private func startMeetingNoRinging(videoCall: Bool, scheduledMeeting: ScheduledMeetingEntity) { - preapareAudioForCall() + prepareAudioForCall() callUseCase.startCallNoRinging(for: scheduledMeeting, enableVideo: videoCall, enableAudio: true) { [weak self] result in guard let self else { return } updateNavigationBarButtonsBeforeStartCall() switch result { case .success: startMeetingUI(isVideoEnabled: videoCall, - isSpeakerEnabled: self.chatRoom.isMeeting || videoCall) + isSpeakerEnabled: chatRoom.isMeeting || videoCall) + case .failure: + MEGALogDebug("Cannot start no ringing call for scheduled meeting") + } + } + } + + private func startMeetingInWaitingRoomChat(videoCall: Bool, scheduledMeeting: ScheduledMeetingEntity) { + prepareAudioForCall() + callUseCase.startMeetingInWaitingRoomChat(for: scheduledMeeting, enableVideo: videoCall, enableAudio: true) { [weak self] result in + guard let self else { return } + updateNavigationBarButtonsBeforeStartCall() + switch result { + case .success: + startMeetingUI(isVideoEnabled: videoCall, + isSpeakerEnabled: chatRoom.isMeeting || videoCall) case .failure: MEGALogDebug("Cannot start no ringing call for scheduled meeting") } @@ -1162,14 +1180,14 @@ class ChatViewController: MessagesViewController { } private func startOutGoingCall(isVideoEnabled: Bool) { - preapareAudioForCall() + prepareAudioForCall() callUseCase.startCall(for: chatRoom.chatId, enableVideo: isVideoEnabled, enableAudio: !chatRoom.isMeeting) { [weak self] result in guard let self else { return } updateNavigationBarButtonsBeforeStartCall() switch result { case .success: startMeetingUI(isVideoEnabled: isVideoEnabled, - isSpeakerEnabled: false) + isSpeakerEnabled: false) case .failure: MEGALogDebug("Cannot start outgoing call") } @@ -1177,14 +1195,14 @@ class ChatViewController: MessagesViewController { } private func answerCall() { - preapareAudioForCall() + prepareAudioForCall() callUseCase.answerCall(for: chatRoom.chatId) { [weak self] result in guard let self else { return } updateNavigationBarButtonsBeforeStartCall() switch result { case .success: startMeetingUI(isVideoEnabled: false, - isSpeakerEnabled: false) + isSpeakerEnabled: false) case .failure: MEGALogDebug("Cannot answer call") } @@ -1203,7 +1221,7 @@ class ChatViewController: MessagesViewController { if activeCall.status == .userNoPresent { startOutGoingCall(isVideoEnabled: isVideoEnabled) } else { - let isSpeakerEnabled = AVAudioSession.sharedInstance().mnz_isOutputEqual(toPortType: .builtInSpeaker) + let isSpeakerEnabled = AVAudioSession.sharedInstance().isOutputEqualToPortType(.builtInSpeaker) startMeetingUI(isVideoEnabled: isVideoEnabled, isSpeakerEnabled: isSpeakerEnabled) } } @@ -1220,7 +1238,12 @@ class ChatViewController: MessagesViewController { isSpeakerEnabled: isSpeakerEnabled).start() } - private func preapareAudioForCall() { + private func openWaitingRoom() { + guard let scheduledMeeting = scheduledMeetingUseCase.scheduledMeetingsByChat(chatId: chatRoom.chatId).first else { return } + WaitingRoomViewRouter(presenter: self, scheduledMeeting: scheduledMeeting).start() + } + + private func prepareAudioForCall() { shouldDisableAudioVideoCalling = true updateRightBarButtons() @@ -1236,8 +1259,10 @@ class ChatViewController: MessagesViewController { guard let call = MEGAChatSdk.shared.chatCall(forChatId: chatRoom.chatId) else { let reachable = MEGAReachabilityManager.isReachable() let existsActiveCall = MEGAChatSdk.shared.mnz_existsActiveCall - - if chatRoom.isMeeting && !shouldRing { + + if chatContentViewModel.shouldOpenWaitingRoom() { + openWaitingRoom() + } else if chatRoom.isMeeting && !shouldRing { chatContentViewModel.dispatch(.startMeetingNoRinging(videoCall, shouldDisableAudioVideoCalling, isVoiceRecordingInProgress, reachable, existsActiveCall)) } else { chatContentViewModel.dispatch(.startOutGoingCall(videoCall, shouldDisableAudioVideoCalling, isVoiceRecordingInProgress, reachable, existsActiveCall)) @@ -1246,7 +1271,9 @@ class ChatViewController: MessagesViewController { return } - if call.isRinging || call.status == .userNoPresent { + if chatContentViewModel.shouldOpenWaitingRoom() { + openWaitingRoom() + } else if call.isRinging || call.status == .userNoPresent { answerCall() } else { joinActiveCall(isVideoEnabled: videoCall) diff --git a/iMEGA/Chat Messages/MegaDataFormatter.swift b/iMEGA/Chat Messages/MegaDataFormatter.swift index 61c5713f0f..91bc1f4c5d 100644 --- a/iMEGA/Chat Messages/MegaDataFormatter.swift +++ b/iMEGA/Chat Messages/MegaDataFormatter.swift @@ -1,4 +1,3 @@ - struct MegaDataFormatter { // MARK: - Properties diff --git a/iMEGA/Chat Messages/ReactedEmojisUsersListViewController.swift b/iMEGA/Chat Messages/ReactedEmojisUsersListViewController.swift index eb10de6fb4..3b8b4c02d3 100644 --- a/iMEGA/Chat Messages/ReactedEmojisUsersListViewController.swift +++ b/iMEGA/Chat Messages/ReactedEmojisUsersListViewController.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import PanModal protocol ReactedEmojisUsersListViewControllerDelegate: AnyObject { @@ -107,8 +107,7 @@ class ReactedEmojisUsersListViewController: UIViewController { } private func userhandleList(forEmoji emoji: String, chatId: UInt64, messageId: UInt64) -> [UInt64] { - guard let userHandleList = MEGASdkManager - .sharedMEGAChatSdk() + guard let userHandleList = MEGAChatSdk.shared .reactionUsers(forChat: chatId, messageId: messageId, reaction: emoji) else { MEGALogDebug("user handle list for emoji \(emoji) is empty") return [] @@ -161,7 +160,7 @@ extension ReactedEmojisUsersListViewController: ReactedUsersListPageViewControll func userName(forHandle handle: UInt64) -> String? { guard let myHandle = MEGASdk.currentUserHandle()?.uint64Value, myHandle != handle else { - if let myFullName = MEGASdkManager.sharedMEGAChatSdk().myFullname { + if let myFullName = MEGAChatSdk.shared.myFullname { return String(format: "%@ (%@)", myFullName, Strings.Localizable.me) } diff --git a/iMEGA/Chat Messages/ReactedUsersListPageViewController.swift b/iMEGA/Chat Messages/ReactedUsersListPageViewController.swift index 7a4e902a97..3f9c96091e 100644 --- a/iMEGA/Chat Messages/ReactedUsersListPageViewController.swift +++ b/iMEGA/Chat Messages/ReactedUsersListPageViewController.swift @@ -1,4 +1,3 @@ - import UIKit protocol ReactedUsersListPageViewControllerDelegate: AnyObject { diff --git a/iMEGA/Chat Messages/ViewModel/ChatContentViewModel.swift b/iMEGA/Chat Messages/ViewModel/ChatContentViewModel.swift index cd288138a1..5777a670b5 100644 --- a/iMEGA/Chat Messages/ViewModel/ChatContentViewModel.swift +++ b/iMEGA/Chat Messages/ViewModel/ChatContentViewModel.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n import MEGAPresentation enum ChatContentAction: ActionType { @@ -26,6 +27,7 @@ final class ChatContentViewModel: ViewModelType { case enableAudioVideoButtons(_ enable: Bool) case showCallEndTimerIfNeeded(_ call: CallEntity) case startMeetingNoRinging(_ videoCall: Bool, _ scheduledMeeting: ScheduledMeetingEntity) + case startMeetingInWaitingRoomChat(_ videoCall: Bool, _ scheduledMeeting: ScheduledMeetingEntity) case hideStartOrJoinCallButton(_ hide: Bool) } @@ -65,7 +67,11 @@ final class ChatContentViewModel: ViewModelType { onUpdateNavigationBarButtonItems(disableCalling, isVoiceRecordingInProgress, reachable, activeCall) case .startMeetingNoRinging(let videoCall, let disableCalling, let isVoiceRecordingInProgress, let reachable, let activeCall): - startMeetingNoRinging(videoCall, disableCalling, isVoiceRecordingInProgress, reachable, activeCall) + if chatRoom.isWaitingRoomEnabled { + startMeetingInWaitingRoomChat(videoCall, disableCalling, isVoiceRecordingInProgress, reachable, activeCall) + } else { + startMeetingNoRinging(videoCall, disableCalling, isVoiceRecordingInProgress, reachable, activeCall) + } case .startOutGoingCall(let isVideoEnabled, let disableCalling, let isVoiceRecordingInProgress, let reachable, let activeCall): startOutGoingCall(isVideoEnabled, disableCalling, isVoiceRecordingInProgress, reachable, activeCall) @@ -76,6 +82,13 @@ final class ChatContentViewModel: ViewModelType { } } + // MARK: - Public + + func shouldOpenWaitingRoom() -> Bool { + let isModerator = chatRoom.ownPrivilege == .moderator + return !isModerator && chatRoom.isWaitingRoomEnabled && isWaitingRoomFeatureEnabled + } + // MARK: - Private private func updateContentIfNeeded() { @@ -117,6 +130,24 @@ final class ChatContentViewModel: ViewModelType { } } + private func startMeetingInWaitingRoomChat( + _ videoCall: Bool, + _ disableCalling: Bool, + _ isVoiceRecordingInProgress: Bool, + _ reachable: Bool, + _ activeCall: Bool + ) { + Task { + let shouldEnable = await shouldEnableAudioVideoButtons(disableCalling, isVoiceRecordingInProgress, + reachable, activeCall) + let scheduledMeetings = await scheduledMeetingUseCase.scheduledMeetings(by: chatRoom.chatId) + + if shouldEnable && scheduledMeetings.isNotEmpty { + await startMeetingInWaitingRoomChat(videoCall, scheduledMeetings[0]) + } + } + } + private func startOutGoingCall(_ videoEnabled: Bool, _ disableCalling: Bool, _ isVoiceRecordingInProgress: Bool, @@ -178,6 +209,11 @@ final class ChatContentViewModel: ViewModelType { invokeCommand?(.startMeetingNoRinging(videoCall, scheduledMeeting)) } + @MainActor + private func startMeetingInWaitingRoomChat(_ videoCall: Bool, _ scheduledMeeting: ScheduledMeetingEntity) { + invokeCommand?(.startMeetingInWaitingRoomChat(videoCall, scheduledMeeting)) + } + @MainActor private func startOutGoingCall(_ enableVideo: Bool) { invokeCommand?(.startOutGoingCall(enableVideo)) diff --git a/iMEGA/Chat Messages/Views/AddToChatAllowAccessCollectionCell.swift b/iMEGA/Chat Messages/Views/AddToChatAllowAccessCollectionCell.swift index 66d6f14127..27475967dc 100644 --- a/iMEGA/Chat Messages/Views/AddToChatAllowAccessCollectionCell.swift +++ b/iMEGA/Chat Messages/Views/AddToChatAllowAccessCollectionCell.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit class AddToChatAllowAccessCollectionCell: UICollectionViewCell { diff --git a/iMEGA/Chat Messages/Views/AddToChatImageCell.swift b/iMEGA/Chat Messages/Views/AddToChatImageCell.swift index 738f87a9a2..69c59c436a 100644 --- a/iMEGA/Chat Messages/Views/AddToChatImageCell.swift +++ b/iMEGA/Chat Messages/Views/AddToChatImageCell.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit class AddToChatImageCell: UICollectionViewCell { diff --git a/iMEGA/Chat Messages/Views/AddToChatMenu.swift b/iMEGA/Chat Messages/Views/AddToChatMenu.swift index acd6a08ba8..4477f3f1e6 100644 --- a/iMEGA/Chat Messages/Views/AddToChatMenu.swift +++ b/iMEGA/Chat Messages/Views/AddToChatMenu.swift @@ -1,4 +1,3 @@ - struct AddToChatMenu: Codable { enum MenuNameKey: String { case photos = "photo.navigation.title" diff --git a/iMEGA/Chat Messages/Views/AddToChatMenuItemsView.swift b/iMEGA/Chat Messages/Views/AddToChatMenuItemsView.swift index 42320d25c2..05fbff28cb 100644 --- a/iMEGA/Chat Messages/Views/AddToChatMenuItemsView.swift +++ b/iMEGA/Chat Messages/Views/AddToChatMenuItemsView.swift @@ -1,4 +1,3 @@ - import UIKit protocol AddToChatMenuItemsViewDelegate: AnyObject { diff --git a/iMEGA/Chat Messages/Views/AddToChatMenuView.swift b/iMEGA/Chat Messages/Views/AddToChatMenuView.swift index d04149ecb2..0036f7c512 100644 --- a/iMEGA/Chat Messages/Views/AddToChatMenuView.swift +++ b/iMEGA/Chat Messages/Views/AddToChatMenuView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit class AddToChatMenuView: UIView { @@ -19,7 +19,7 @@ class AddToChatMenuView: UIView { } imageView.image = UIImage(named: menu.imageKey) - label.text = NSLocalizedString(menu.nameKey, comment: "") + label.text = Strings.localized(menu.nameKey, comment: "") imageBackgroundView.isHidden = false } } diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/ChatInputBar.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/ChatInputBar.swift index add6f8f7aa..d6b01e7386 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/ChatInputBar.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/ChatInputBar.swift @@ -1,4 +1,3 @@ - import simd import UIKit diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputBar.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputBar.swift index 1fa8e0001b..956d7c5a92 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputBar.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputBar.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit protocol MessageInputBarDelegate: AnyObject { diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputBarComponentsSizeCalculator.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputBarComponentsSizeCalculator.swift index db15db7a3f..ec77957764 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputBarComponentsSizeCalculator.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputBarComponentsSizeCalculator.swift @@ -1,4 +1,3 @@ - final class MessageInputBarComponentsSizeCalculator { func calculateTypingLabelSize(fitSize: CGSize) -> CGSize { let auxLabel = UILabel() diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputTextBackgroundView.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputTextBackgroundView.swift index 5e6ac1f83c..27736756cb 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputTextBackgroundView.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageInputTextBackgroundView.swift @@ -1,4 +1,3 @@ - class MessageInputTextBackgroundView: UIView { var maxCornerRadius: CGFloat = .greatestFiniteMagnitude diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageTextView.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageTextView.swift index c68f8a4519..d35f943e59 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageTextView.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/Text Input/MessageTextView.swift @@ -1,3 +1,4 @@ +import MEGAL10n class MessageTextView: UITextView { diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/AudioRecordingInputBar.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/AudioRecordingInputBar.swift index 3a31faba1a..66ee70a17b 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/AudioRecordingInputBar.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/AudioRecordingInputBar.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit protocol AudioRecordingInputBarDelegate: AnyObject { diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/AudioWavesView.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/AudioWavesView.swift index 7d0d31b5a2..91a99a0689 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/AudioWavesView.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/AudioWavesView.swift @@ -1,4 +1,3 @@ - import UIKit class AudioWavesView: UIView { diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/EnlargementView.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/EnlargementView.swift index e45f15632b..889a79d363 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/EnlargementView.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/EnlargementView.swift @@ -1,4 +1,3 @@ - class EnlargementView: UIView { @IBOutlet weak var nonSelectionView: UIView! @IBOutlet weak var selectionView: UIView! diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/FingerLiftupGestureRecognizer.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/FingerLiftupGestureRecognizer.swift index bbda5b6df5..6e713236b2 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/FingerLiftupGestureRecognizer.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/FingerLiftupGestureRecognizer.swift @@ -1,4 +1,3 @@ - /// This gesture will help the user to know when the finger is lifted from the screen. /// Please note: This gesture can be cancelled if the user of the gesture is not interested in the event temporarily. class FingerLiftupGestureRecognizer: UIGestureRecognizer { diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/MeterTable.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/MeterTable.swift index b05cba11ae..d9b57ecae0 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/MeterTable.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/MeterTable.swift @@ -1,4 +1,3 @@ - /* File: MeterTable.h Abstract: Class for handling conversion from linear scale to dB diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/TapAndHoldMessageView.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/TapAndHoldMessageView.swift index 152f40002c..4dd3e73adf 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/TapAndHoldMessageView.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/TapAndHoldMessageView.swift @@ -1,5 +1,5 @@ - import Foundation +import MEGAL10n class TapAndHoldMessageView: UIView { diff --git a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/VoiceClipInputBar.swift b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/VoiceClipInputBar.swift index 064c4a7abd..da470538fe 100644 --- a/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/VoiceClipInputBar.swift +++ b/iMEGA/Chat Messages/Views/Chat Input Bar/Voice Input/VoiceClipInputBar.swift @@ -1,4 +1,3 @@ - import UIKit protocol VoiceClipInputBarDelegate: AnyObject { diff --git a/iMEGA/Chat Messages/Views/ChatGiphyCollectionViewCell.swift b/iMEGA/Chat Messages/Views/ChatGiphyCollectionViewCell.swift index 2cb47c2164..2d47ec75ff 100644 --- a/iMEGA/Chat Messages/Views/ChatGiphyCollectionViewCell.swift +++ b/iMEGA/Chat Messages/Views/ChatGiphyCollectionViewCell.swift @@ -34,7 +34,7 @@ class ChatGiphyCollectionViewCell: MessageContentCell { setupConstraints() } - override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { + override func configure(with message: any MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { super.configure(with: message, at: indexPath, and: messagesCollectionView) guard let chatMessage = message as? ChatMessage else { return @@ -59,7 +59,7 @@ open class ChatGiphyCollectionViewSizeCalculator: MessageSizeCalculator { configureAccessoryView() } - override open func messageContainerSize(for message: MessageType) -> CGSize { + override open func messageContainerSize(for message: any MessageType) -> CGSize { switch message.kind { case .custom: return size(for: message) @@ -68,7 +68,7 @@ open class ChatGiphyCollectionViewSizeCalculator: MessageSizeCalculator { } } - private func size(for message: MessageType) -> CGSize { + private func size(for message: some MessageType) -> CGSize { let maxHeight = UIDevice.current.mnz_maxSideForChatBubble(withMedia: true) let maxWidth = min(maxHeight, messageContainerMaxWidth(for: message)) diff --git a/iMEGA/Chat Messages/Views/ChatLocationCollectionViewCell.swift b/iMEGA/Chat Messages/Views/ChatLocationCollectionViewCell.swift index a1fde7ce5e..29adc028d1 100644 --- a/iMEGA/Chat Messages/Views/ChatLocationCollectionViewCell.swift +++ b/iMEGA/Chat Messages/Views/ChatLocationCollectionViewCell.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MessageKit class ChatLocationCollectionViewCell: MessageContentCell { @@ -21,7 +22,7 @@ class ChatLocationCollectionViewCell: MessageContentCell { setupConstraints() } - override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { + override func configure(with message: any MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { super.configure(with: message, at: indexPath, and: messagesCollectionView) guard let chatMessage = message as? ChatMessage else { return @@ -60,11 +61,11 @@ open class ChatlocationCollectionViewSizeCalculator: MessageSizeCalculator { configureAccessoryView() } - open override func messageContainerMaxWidth(for message: MessageType) -> CGFloat { + open override func messageContainerMaxWidth(for message: any MessageType) -> CGFloat { min(UIDevice.current.mnz_maxSideForChatBubble(withMedia: true), 260) } - open override func messageContainerSize(for message: MessageType) -> CGSize { + open override func messageContainerSize(for message: any MessageType) -> CGSize { switch message.kind { case .custom: let fitSize = CGSize(width: messageContainerMaxWidth(for: message), height: .greatestFiniteMagnitude) @@ -74,7 +75,7 @@ open class ChatlocationCollectionViewSizeCalculator: MessageSizeCalculator { } } - private func calculateDynamicSize(for message: MessageType, fitSize: CGSize) -> CGSize { + private func calculateDynamicSize(for message: any MessageType, fitSize: CGSize) -> CGSize { calculateTitleLabel.font = UIFont.preferredFont(style: .subheadline, weight: .medium) calculateTitleLabel.text = Strings.Localizable.pinnedLocation calculateSubtitleLabel.font = UIFont.preferredFont(forTextStyle: .footnote) diff --git a/iMEGA/Chat Messages/Views/ChatManagmentTypeCollectionViewCell.swift b/iMEGA/Chat Messages/Views/ChatManagmentTypeCollectionViewCell.swift index 270ad83da5..9e345d90b5 100644 --- a/iMEGA/Chat Messages/Views/ChatManagmentTypeCollectionViewCell.swift +++ b/iMEGA/Chat Messages/Views/ChatManagmentTypeCollectionViewCell.swift @@ -7,7 +7,7 @@ class ChatManagmentTypeCollectionViewCell: TextMessageCell { avatarView.backgroundColor = .clear } - override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { + override func configure(with message: any MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { guard let chatMessage = message as? ChatMessage else { return } super.configure(with: ConcreteMessageType(chatMessage: chatMessage), at: indexPath, and: messagesCollectionView) } @@ -15,7 +15,7 @@ class ChatManagmentTypeCollectionViewCell: TextMessageCell { open class ChatManagmentTypeCollectionViewSizeCalculator: TextMessageSizeCalculator { - open override func messageContainerSize(for message: MessageType) -> CGSize { + open override func messageContainerSize(for message: any MessageType) -> CGSize { guard let chatMessage = message as? ChatMessage else { return .zero } return super.messageContainerSize(for: ConcreteMessageType(chatMessage: chatMessage)) } diff --git a/iMEGA/Chat Messages/Views/ChatMediaCollectionViewCell.swift b/iMEGA/Chat Messages/Views/ChatMediaCollectionViewCell.swift index b93dcd0942..253389dd96 100644 --- a/iMEGA/Chat Messages/Views/ChatMediaCollectionViewCell.swift +++ b/iMEGA/Chat Messages/Views/ChatMediaCollectionViewCell.swift @@ -68,12 +68,12 @@ class ChatMediaCollectionViewCell: MessageContentCell, MEGATransferDelegate { override public init(frame: CGRect) { super.init(frame: frame) - MEGASdkManager.sharedMEGASdk().add(self) + MEGASdk.sharedSdk.add(self) } public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - MEGASdkManager.sharedMEGASdk().add(self) + MEGASdk.sharedSdk.add(self) } // MARK: - Methods @@ -108,7 +108,7 @@ class ChatMediaCollectionViewCell: MessageContentCell, MEGATransferDelegate { setupConstraints() } - override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { + override func configure(with message: any MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { super.configure(with: message, at: indexPath, and: messagesCollectionView) guard let chatMessage = message as? ChatMessage else { @@ -220,11 +220,11 @@ open class ChatMediaCollectionViewSizeCalculator: MessageSizeCalculator { configureAccessoryView() } - override open func messageContainerMaxWidth(for message: MessageType) -> CGFloat { + override open func messageContainerMaxWidth(for message: any MessageType) -> CGFloat { return min(UIDevice.current.mnz_maxSideForChatBubble(withMedia: true), super.messageContainerMaxWidth(for: message)) } - override open func messageContainerSize(for message: MessageType) -> CGSize { + override open func messageContainerSize(for message: any MessageType) -> CGSize { switch message.kind { case .custom: let maxWidth = min(UIDevice.current.mnz_maxSideForChatBubble(withMedia: true), messageContainerMaxWidth(for: message)) diff --git a/iMEGA/Chat Messages/Views/ChatRichPreviewDialogCollectionViewCell.swift b/iMEGA/Chat Messages/Views/ChatRichPreviewDialogCollectionViewCell.swift index 0315db6145..676232ed91 100644 --- a/iMEGA/Chat Messages/Views/ChatRichPreviewDialogCollectionViewCell.swift +++ b/iMEGA/Chat Messages/Views/ChatRichPreviewDialogCollectionViewCell.swift @@ -6,7 +6,7 @@ class ChatRichPreviewDialogCollectionViewCell: TextMessageCell { var megaMessage: MEGAChatMessage? var indexPath: IndexPath? - override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { + override func configure(with message: any MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { guard let chatMessage = message as? ChatMessage else { return @@ -39,15 +39,15 @@ class ChatRichPreviewDialogCollectionViewCell: TextMessageCell { // Trigger action if richPreviewDialogView.alwaysAllowButton.frame.contains(touchLocation) { if megaMessage.warningDialog == .confirmation { - MEGASdkManager.sharedMEGASdk().enableRichPreviews(false) + MEGASdk.shared.enableRichPreviews(false) } else { - MEGASdkManager.sharedMEGASdk().enableRichPreviews(true) + MEGASdk.shared.enableRichPreviews(true) } megaMessage.warningDialog = .none } else if richPreviewDialogView.notNowButton.frame.contains(touchLocation) { megaMessage.warningDialog = .dismiss chatVC.richLinkWarningCounterValue += 1 - MEGASdkManager.sharedMEGASdk().setRichLinkWarningCounterValue(chatVC.richLinkWarningCounterValue) + MEGASdk.shared.setRichLinkWarningCounterValue(chatVC.richLinkWarningCounterValue) } else if richPreviewDialogView.neverButton.frame.contains(touchLocation) { megaMessage.warningDialog = .confirmation } @@ -64,11 +64,11 @@ class ChatRichPreviewDialogCollectionViewCell: TextMessageCell { open class ChatRichPreviewDialogCollectionViewSizeCalculator: TextMessageSizeCalculator { var richPreviewDialogView: RichPreviewDialogView = RichPreviewDialogView() - override open func messageContainerMaxWidth(for message: MessageType) -> CGFloat { + override open func messageContainerMaxWidth(for message: any MessageType) -> CGFloat { return min(UIDevice.current.mnz_maxSideForChatBubble(withMedia: true), super.messageContainerMaxWidth(for: message)) } - open override func messageContainerSize(for message: MessageType) -> CGSize { + open override func messageContainerSize(for message: any MessageType) -> CGSize { guard let chatMessage = message as? ChatMessage else { return .zero } diff --git a/iMEGA/Chat Messages/Views/ChatRichPreviewMediaCollectionViewCell.swift b/iMEGA/Chat Messages/Views/ChatRichPreviewMediaCollectionViewCell.swift index 04ccf9c4b1..e0df5a1af3 100644 --- a/iMEGA/Chat Messages/Views/ChatRichPreviewMediaCollectionViewCell.swift +++ b/iMEGA/Chat Messages/Views/ChatRichPreviewMediaCollectionViewCell.swift @@ -1,8 +1,11 @@ +import ChatRepo import Foundation +import MEGAL10n +import MEGASDKRepo import MessageKit struct ConcreteMessageType: MessageType { - let sender: SenderType + let sender: any SenderType let messageId: String let sentDate: Date var kind: MessageKind @@ -27,15 +30,15 @@ class ChatRichPreviewMediaCollectionViewCell: TextMessageCell, MEGARequestDelega public override init(frame: CGRect) { super.init(frame: frame) - MEGASdkManager.sharedMEGASdk().add(self) + MEGASdk.shared.add(self) } public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - MEGASdkManager.sharedMEGASdk().add(self) + MEGASdk.shared.add(self) } - override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { + override func configure(with message: any MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { guard let chatMessage = message as? ChatMessage else { return } @@ -58,7 +61,7 @@ class ChatRichPreviewMediaCollectionViewCell: TextMessageCell, MEGARequestDelega case .fileLink: if megaMessage.richNumber == nil { - MEGASdkManager.sharedMEGASdk().publicNode(forMegaFileLink: megaLink.mnz_MEGAURL(), delegate: MEGAGetPublicNodeRequestDelegate(completion: { (request, error) in + MEGASdk.shared.publicNode(forMegaFileLink: megaLink.mnz_MEGAURL(), delegate: MEGAGetPublicNodeRequestDelegate(completion: { (request, error) in let visibleIndexPaths = messagesCollectionView.indexPathsForVisibleItems guard visibleIndexPaths.contains(indexPath), error?.type == .apiOk else { return @@ -79,11 +82,13 @@ class ChatRichPreviewMediaCollectionViewCell: TextMessageCell, MEGARequestDelega case .folderLink: if megaMessage.richNumber == nil { - MEGASdkManager.sharedMEGASdk().getPublicLinkInformation(withFolderLink: megaLink.mnz_MEGAURL(), delegate: MEGAGenericRequestDelegate(completion: { (request, error) in + MEGASdk.shared.getPublicLinkInformation(withFolderLink: megaLink.mnz_MEGAURL(), delegate: RequestDelegate { result in let visibleIndexPaths = messagesCollectionView.indexPathsForVisibleItems - guard visibleIndexPaths.contains(indexPath), error.type == .apiOk else { + + guard visibleIndexPaths.contains(indexPath), case let .success(request) = result else { return } + let totalNumberOfFiles = request.megaFolderInfo.files let numOfVersionedFiles = request.megaFolderInfo.versions let totalFileSize = request.megaFolderInfo.currentSize @@ -99,16 +104,15 @@ class ChatRichPreviewMediaCollectionViewCell: TextMessageCell, MEGARequestDelega } else { messagesCollectionView.reloadItems(at: [indexPath]) } - })) + }) return } richPreviewContentView.isHidden = false case .publicChatLink: if megaMessage.richNumber == nil { - - MEGASdkManager.sharedMEGAChatSdk().checkChatLink(megaLinkURL, delegate: MEGAChatGenericRequestDelegate(completion: { (request, error) in + MEGAChatSdk.shared.checkChatLink(megaLinkURL, delegate: ChatRequestDelegate(successCodes: [.MEGAChatErrorTypeOk, .MegaChatErrorTypeExist]) { result in let visibleIndexPaths = messagesCollectionView.indexPathsForVisibleItems - guard visibleIndexPaths.contains(indexPath), (error.type == .MEGAChatErrorTypeOk || error.type == .MegaChatErrorTypeExist) else { + guard visibleIndexPaths.contains(indexPath), case let .success(request) = result else { return } megaMessage.richString = request.text @@ -119,7 +123,7 @@ class ChatRichPreviewMediaCollectionViewCell: TextMessageCell, MEGARequestDelega } else { messagesCollectionView.reloadItems(at: [indexPath]) } - })) + }) return } richPreviewContentView.isHidden = false @@ -174,11 +178,11 @@ open class ChatRichPreviewMediaCollectionViewSizeCalculator: TextMessageSizeCalc configureAccessoryView() } - override open func messageContainerMaxWidth(for message: MessageType) -> CGFloat { + override open func messageContainerMaxWidth(for message: any MessageType) -> CGFloat { return min(UIDevice.current.mnz_maxSideForChatBubble(withMedia: true), super.messageContainerMaxWidth(for: message)) } - open override func messageContainerSize(for message: MessageType) -> CGSize { + open override func messageContainerSize(for message: any MessageType) -> CGSize { guard let chatMessage = message as? ChatMessage else { return .zero } diff --git a/iMEGA/Chat Messages/Views/ChatTextMessageViewCell.swift b/iMEGA/Chat Messages/Views/ChatTextMessageViewCell.swift index 3b71eed09c..bf79c7d265 100644 --- a/iMEGA/Chat Messages/Views/ChatTextMessageViewCell.swift +++ b/iMEGA/Chat Messages/Views/ChatTextMessageViewCell.swift @@ -6,7 +6,7 @@ class ChatTextMessageViewCell: TextMessageCell { super.setupSubviews() } - override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { + override func configure(with message: any MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { guard let chatMessage = message as? ChatMessage, chatMessage.message.content != nil else { return } @@ -38,11 +38,11 @@ class ChatTextMessageSizeCalculator: TextMessageSizeCalculator { outgoingMessageLabelInsets = UIEdgeInsets(top: 10, left: 18, bottom: 10, right: 18) } - override open func messageContainerMaxWidth(for message: MessageType) -> CGFloat { + override open func messageContainerMaxWidth(for message: any MessageType) -> CGFloat { return min(UIDevice.current.mnz_maxSideForChatBubble(withMedia: true), super.messageContainerMaxWidth(for: message)) } - open override func messageContainerSize(for message: MessageType) -> CGSize { + open override func messageContainerSize(for message: any MessageType) -> CGSize { guard let chatMessage = message as? ChatMessage, chatMessage.message.content != nil else { return .zero } diff --git a/iMEGA/Chat Messages/Views/ChatTitleView.swift b/iMEGA/Chat Messages/Views/ChatTitleView.swift index d52a5584cb..c97d986686 100644 --- a/iMEGA/Chat Messages/Views/ChatTitleView.swift +++ b/iMEGA/Chat Messages/Views/ChatTitleView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit class ChatTitleView: UIView { diff --git a/iMEGA/Chat Messages/Views/ChatUnreadMessagesLabelCollectionCell.swift b/iMEGA/Chat Messages/Views/ChatUnreadMessagesLabelCollectionCell.swift index bfb9eb3c93..21398a593e 100644 --- a/iMEGA/Chat Messages/Views/ChatUnreadMessagesLabelCollectionCell.swift +++ b/iMEGA/Chat Messages/Views/ChatUnreadMessagesLabelCollectionCell.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MessageKit class ChatUnreadMessagesLabelCollectionCell: UICollectionViewCell { @@ -21,7 +22,7 @@ class ChatUnreadMessagesLabelCollectionCellSizeCalculator: MessageSizeCalculator return titleLabel }() - override func messageContainerSize(for message: MessageType) -> CGSize { + override func messageContainerSize(for message: any MessageType) -> CGSize { guard let notificationMessage = message as? ChatNotificationMessage, case .unreadMessage(let count) = notificationMessage.type, count > 0 else { return .zero } diff --git a/iMEGA/Chat Messages/Views/ChatViewAttachmentCell.swift b/iMEGA/Chat Messages/Views/ChatViewAttachmentCell.swift index 56b20af9b8..cb6c0448cc 100644 --- a/iMEGA/Chat Messages/Views/ChatViewAttachmentCell.swift +++ b/iMEGA/Chat Messages/Views/ChatViewAttachmentCell.swift @@ -71,7 +71,7 @@ class ChatViewAttachmentCell: MessageContentCell { attachmentViewModel.set(imageView: imageView) } - override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { + override func configure(with message: any MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { super.configure(with: message, at: indexPath, and: messagesCollectionView) guard let chatMessage = message as? ChatMessage else { @@ -117,7 +117,7 @@ open class ChatViewAttachmentCellCalculator: MessageSizeCalculator { configureAccessoryView() } - open override func messageContainerSize(for message: MessageType) -> CGSize { + open override func messageContainerSize(for message: any MessageType) -> CGSize { guard let chatMessage = message as? ChatMessage else { fatalError("ChatViewAttachmentCellCalculator: wrong type message passed.") } diff --git a/iMEGA/Chat Messages/Views/ChatViewCallCollectionCell.swift b/iMEGA/Chat Messages/Views/ChatViewCallCollectionCell.swift index 48b72a7672..0135f55be8 100644 --- a/iMEGA/Chat Messages/Views/ChatViewCallCollectionCell.swift +++ b/iMEGA/Chat Messages/Views/ChatViewCallCollectionCell.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MessageKit class ChatViewCallCollectionCell: MessageContentCell { @@ -53,7 +54,7 @@ class ChatViewCallCollectionCell: MessageContentCell { } override func configure( - with message: MessageType, + with message: any MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView ) { @@ -96,7 +97,7 @@ class ChatViewCallCollectionCellCalculator: MessageSizeCalculator { incomingMessagePadding = .zero } - override func messageContainerSize(for message: MessageType) -> CGSize { + override func messageContainerSize(for message: any MessageType) -> CGSize { guard let layout = layout else { return .zero } let collectionViewWidth = layout.collectionView?.bounds.width ?? .zero diff --git a/iMEGA/Chat Messages/Views/ChatViewIntroductionHeaderView.swift b/iMEGA/Chat Messages/Views/ChatViewIntroductionHeaderView.swift index 3f8ccf0ceb..a72cd5e5a2 100644 --- a/iMEGA/Chat Messages/Views/ChatViewIntroductionHeaderView.swift +++ b/iMEGA/Chat Messages/Views/ChatViewIntroductionHeaderView.swift @@ -1,7 +1,7 @@ - import CoreGraphics import MEGADomain import MEGAFoundation +import MEGAL10n import MessageKit class ChatViewIntroductionHeaderView: MessageReusableView { diff --git a/iMEGA/Chat Messages/Views/ChatVoiceClipCollectionViewCell.swift b/iMEGA/Chat Messages/Views/ChatVoiceClipCollectionViewCell.swift index 90435420f5..c80fb663a9 100644 --- a/iMEGA/Chat Messages/Views/ChatVoiceClipCollectionViewCell.swift +++ b/iMEGA/Chat Messages/Views/ChatVoiceClipCollectionViewCell.swift @@ -66,7 +66,7 @@ class ChatVoiceClipCollectionViewCell: AudioMessageCell { durationLabel.adjustsFontForContentSizeCategory = true } - override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { + override func configure(with message: any MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { super.configure(with: message, at: indexPath, and: messagesCollectionView) guard let chatMessage = message as? ChatMessage else { return @@ -165,12 +165,12 @@ open class ChatVoiceClipCollectionViewSizeCalculator: MessageSizeCalculator { incomingMessageBottomLabelAlignment = LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: 0, left: 34, bottom: 0, right: 0)) } - open override func messageContainerSize(for message: MessageType) -> CGSize { + open override func messageContainerSize(for message: any MessageType) -> CGSize { let fitSize = CGSize(width: messageContainerMaxWidth(for: message), height: .greatestFiniteMagnitude) return calculateDynamicSize(for: message, fitSize: fitSize) } - private func calculateDynamicSize(for message: MessageType, fitSize: CGSize) -> CGSize { + private func calculateDynamicSize(for message: some MessageType, fitSize: CGSize) -> CGSize { calculateDurationLabel.textAlignment = .left calculateDurationLabel.font = .preferredFont(forTextStyle: .subheadline) calculateDurationLabel.text = "00:00" diff --git a/iMEGA/Chat Messages/Views/ContactLinkCollectionViewCell.swift b/iMEGA/Chat Messages/Views/ContactLinkCollectionViewCell.swift index c8420a442f..2b22f2210e 100644 --- a/iMEGA/Chat Messages/Views/ContactLinkCollectionViewCell.swift +++ b/iMEGA/Chat Messages/Views/ContactLinkCollectionViewCell.swift @@ -10,7 +10,7 @@ class ContactLinkCollectionViewCell: TextMessageCell { return view }() - override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { + override func configure(with message: any MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) { guard let chatMessage = message as? ChatMessage else { return } @@ -95,11 +95,11 @@ open class ChatContactLinkCollectionViewSizeCalculator: TextMessageSizeCalculato configureAccessoryView() } - override open func messageContainerMaxWidth(for message: MessageType) -> CGFloat { + override open func messageContainerMaxWidth(for message: any MessageType) -> CGFloat { min(UIDevice.current.mnz_maxSideForChatBubble(withMedia: true), super.messageContainerMaxWidth(for: message)) } - open override func messageContainerSize(for message: MessageType) -> CGSize { + open override func messageContainerSize(for message: any MessageType) -> CGSize { guard let chatMessage = message as? ChatMessage else { return .zero } diff --git a/iMEGA/Chat Messages/Views/GeoLocationView.swift b/iMEGA/Chat Messages/Views/GeoLocationView.swift index bcac2444c3..0fc026b92f 100644 --- a/iMEGA/Chat Messages/Views/GeoLocationView.swift +++ b/iMEGA/Chat Messages/Views/GeoLocationView.swift @@ -1,4 +1,3 @@ - import UIKit class GeoLocationView: UIView { diff --git a/iMEGA/Chat Messages/Views/JoinInputBar.swift b/iMEGA/Chat Messages/Views/JoinInputBar.swift index 08da7da901..ae5f38cca5 100644 --- a/iMEGA/Chat Messages/Views/JoinInputBar.swift +++ b/iMEGA/Chat Messages/Views/JoinInputBar.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit class JoinInputBar: UIView { diff --git a/iMEGA/Chat Messages/Views/MessageSizeCalculator+AccessoryView.swift b/iMEGA/Chat Messages/Views/MessageSizeCalculator+AccessoryView.swift index 3c6bbfe7ef..360feaeace 100644 --- a/iMEGA/Chat Messages/Views/MessageSizeCalculator+AccessoryView.swift +++ b/iMEGA/Chat Messages/Views/MessageSizeCalculator+AccessoryView.swift @@ -1,4 +1,3 @@ - import MessageKit extension MessageSizeCalculator { diff --git a/iMEGA/Chat Messages/Views/ReactionView/Emoji.swift b/iMEGA/Chat Messages/Views/ReactionView/Emoji.swift index 73abfba3f9..4b862297a4 100644 --- a/iMEGA/Chat Messages/Views/ReactionView/Emoji.swift +++ b/iMEGA/Chat Messages/Views/ReactionView/Emoji.swift @@ -1,4 +1,3 @@ - struct Emoji: Codable { let category: Int let representation: String diff --git a/iMEGA/Chat Messages/Views/ReactionView/EmojiCarousalView.swift b/iMEGA/Chat Messages/Views/ReactionView/EmojiCarousalView.swift index 0e897463f8..f16717517c 100644 --- a/iMEGA/Chat Messages/Views/ReactionView/EmojiCarousalView.swift +++ b/iMEGA/Chat Messages/Views/ReactionView/EmojiCarousalView.swift @@ -1,4 +1,3 @@ - import UIKit protocol EmojiCarousalViewDelegate: AnyObject { diff --git a/iMEGA/Chat Messages/Views/ReactionView/EmojiReactionCollectionCell.swift b/iMEGA/Chat Messages/Views/ReactionView/EmojiReactionCollectionCell.swift index 30690b3da9..869dcad81c 100644 --- a/iMEGA/Chat Messages/Views/ReactionView/EmojiReactionCollectionCell.swift +++ b/iMEGA/Chat Messages/Views/ReactionView/EmojiReactionCollectionCell.swift @@ -1,4 +1,3 @@ - import UIKit class EmojiReactionCollectionCell: UICollectionViewCell { diff --git a/iMEGA/Chat Messages/Views/ReactionView/MessageOptionItemTableCell.swift b/iMEGA/Chat Messages/Views/ReactionView/MessageOptionItemTableCell.swift index 531747d336..3e494a2560 100644 --- a/iMEGA/Chat Messages/Views/ReactionView/MessageOptionItemTableCell.swift +++ b/iMEGA/Chat Messages/Views/ReactionView/MessageOptionItemTableCell.swift @@ -1,4 +1,3 @@ - protocol MessageOptionItemTableCellDelegate: AnyObject { func setImageView(_ imageView: UIImageView, forIndex index: Int) func setLabel(_ label: UILabel, forIndex index: Int) diff --git a/iMEGA/Chat Messages/Views/ReactionView/MessageReactionReusableView.swift b/iMEGA/Chat Messages/Views/ReactionView/MessageReactionReusableView.swift index a406bdf347..66a3441fd4 100644 --- a/iMEGA/Chat Messages/Views/ReactionView/MessageReactionReusableView.swift +++ b/iMEGA/Chat Messages/Views/ReactionView/MessageReactionReusableView.swift @@ -35,8 +35,8 @@ class MessageReactionReusableView: MessageReusableView { reactionContainerView.pin.vertically().horizontally(pin.safeArea) } - func isFromCurrentSender(message: MessageType) -> Bool { - return UInt64(message.sender.senderId) == MEGASdkManager.sharedMEGAChatSdk().myUserHandle + func isFromCurrentSender(message: any MessageType) -> Bool { + return UInt64(message.sender.senderId) == MEGAChatSdk.shared.myUserHandle } } diff --git a/iMEGA/Chat Messages/Views/RichPreviewContentView.swift b/iMEGA/Chat Messages/Views/RichPreviewContentView.swift index 6009d33d86..d12073ec34 100644 --- a/iMEGA/Chat Messages/Views/RichPreviewContentView.swift +++ b/iMEGA/Chat Messages/Views/RichPreviewContentView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit class RichPreviewContentView: UIView { diff --git a/iMEGA/Chat Messages/Views/RichPreviewDialogView.swift b/iMEGA/Chat Messages/Views/RichPreviewDialogView.swift index a19ef6d9d2..519373b701 100644 --- a/iMEGA/Chat Messages/Views/RichPreviewDialogView.swift +++ b/iMEGA/Chat Messages/Views/RichPreviewDialogView.swift @@ -1,4 +1,5 @@ import FlexLayout +import MEGAL10n import UIKit class RichPreviewDialogView: UIView { diff --git a/iMEGA/Chat/ChatAttachedContactsViewController.h b/iMEGA/Chat/ChatAttachedContactsViewController.h index 7ac226a06e..cbd041a8dd 100644 --- a/iMEGA/Chat/ChatAttachedContactsViewController.h +++ b/iMEGA/Chat/ChatAttachedContactsViewController.h @@ -1,4 +1,3 @@ - @class MEGAChatMessage; @interface ChatAttachedContactsViewController : UIViewController diff --git a/iMEGA/Chat/ChatAttachedContactsViewController.m b/iMEGA/Chat/ChatAttachedContactsViewController.m index fa6ff45836..d90d2a71b7 100644 --- a/iMEGA/Chat/ChatAttachedContactsViewController.m +++ b/iMEGA/Chat/ChatAttachedContactsViewController.m @@ -1,4 +1,3 @@ - #import "ChatAttachedContactsViewController.h" #import "MEGAInviteContactRequestDelegate.h" @@ -11,6 +10,8 @@ #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @interface ChatAttachedContactsViewController () @property (weak, nonatomic) IBOutlet UITableView *tableView; @@ -58,10 +59,10 @@ - (void)viewWillDisappear:(BOOL)animated { - (void)setupAttachedContacts { self.backBarButtonItem.image = self.backBarButtonItem.image.imageFlippedForRightToLeftLayoutDirection; self.navigationItem.leftBarButtonItem = self.backBarButtonItem; - self.editBarButtonItem.title = NSLocalizedString(@"select", @"Caption of a button to select files"); + self.editBarButtonItem.title = LocalizedString(@"select", @"Caption of a button to select files"); self.navigationItem.rightBarButtonItems = @[self.editBarButtonItem]; - self.addBarButtonItem.title = NSLocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); + self.addBarButtonItem.title = LocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); [self.addBarButtonItem setTitleTextAttributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleBody weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_redForTraitCollection:self.traitCollection]} forState:UIControlStateNormal]; UIBarButtonItem *flexibleItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; [self setToolbarItems:@[flexibleItem, self.addBarButtonItem]]; @@ -103,7 +104,7 @@ - (void)setNavigationBarButtonItemsEnabled:(BOOL)boolValue { - (void)setNavigationBarTitle { [self updatePromptTitle]; - NSString *navigationTitle = NSLocalizedString(@"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."); + NSString *navigationTitle = LocalizedString(@"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."); navigationTitle = [navigationTitle stringByReplacingOccurrencesOfString:@"%s" withString:[NSString stringWithFormat:@"%lu", (unsigned long)self.message.usersCount]]; self.navigationItem.title = navigationTitle; } @@ -120,11 +121,11 @@ - (void)updatePromptTitle { - (NSString *)titleForPromptWithCountOfContacts:(NSNumber *)count { NSString *promptString; if (count.unsignedIntegerValue == 0) { - promptString = NSLocalizedString(@"select", @"Button that allows you to select a given folder"); + promptString = LocalizedString(@"select", @"Button that allows you to select a given folder"); } else if (count.unsignedIntegerValue == 1) { - promptString = NSLocalizedString(@"oneContact", @""); + promptString = LocalizedString(@"oneContact", @""); } else { - promptString = NSLocalizedString(@"XContactsSelected", @"[X] will be replaced by a plural number, indicating the total number of contacts the user has"); + promptString = LocalizedString(@"XContactsSelected", @"[X] will be replaced by a plural number, indicating the total number of contacts the user has"); promptString = [promptString stringByReplacingOccurrencesOfString:@"[X]" withString:count.stringValue]; } @@ -135,10 +136,10 @@ - (void)setTableViewEditing:(BOOL)editing animated:(BOOL)animated { [self.tableView setEditing:editing animated:animated]; if (editing) { - self.editBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.editBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); self.addBarButtonItem.enabled = NO; } else { - self.editBarButtonItem.title = NSLocalizedString(@"select", @"Caption of a button to select files"); + self.editBarButtonItem.title = LocalizedString(@"select", @"Caption of a button to select files"); self.addBarButtonItem.enabled = YES; [self.selectedUsersMutableArray removeAllObjects]; @@ -195,7 +196,7 @@ - (IBAction)selectAllAction:(UIBarButtonItem *)sender { [self updatePromptTitle]; selectedUsersCount = self.selectedUsersMutableArray.count; - self.addBarButtonItem.title = (selectedUsersCount > 1) ? NSLocalizedString(@"addContacts", @"Button title shown in empty views when you can 'Add contacts'") : NSLocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); + self.addBarButtonItem.title = (selectedUsersCount > 1) ? LocalizedString(@"addContacts", @"Button title shown in empty views when you can 'Add contacts'") : LocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); self.addBarButtonItem.enabled = (selectedUsersCount == 0) ? NO : YES; [self.tableView reloadData]; } @@ -234,7 +235,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N uint64_t userHandle = [self.message userHandleAtIndex:indexPath.row]; NSString *userBase64Handle = [MEGASdk base64HandleForUserHandle:userHandle]; if ([self.alreadyContactsMutableDictionary objectForKey:userBase64Handle] != nil) { - NSString *alreadyAContactString = NSLocalizedString(@"alreadyAContact", @"Error message displayed when trying to invite a contact who is already added."); + NSString *alreadyAContactString = LocalizedString(@"alreadyAContact", @"Error message displayed when trying to invite a contact who is already added."); cell.shareLabel.text = [alreadyAContactString stringByReplacingOccurrencesOfString:@"%s" withString:currentEmail]; [self.alreadyContactsIndexPathMutableDictionary setObject:indexPath forKey:userBase64Handle]; @@ -275,7 +276,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [self updatePromptTitle]; NSUInteger selectedUsersCount = self.selectedUsersMutableArray.count; - self.addBarButtonItem.title = (selectedUsersCount > 1) ? NSLocalizedString(@"addContacts", @"Button title shown in empty views when you can 'Add contacts'") : NSLocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); + self.addBarButtonItem.title = (selectedUsersCount > 1) ? LocalizedString(@"addContacts", @"Button title shown in empty views when you can 'Add contacts'") : LocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); self.addBarButtonItem.enabled = (selectedUsersCount == 0) ? NO : YES; return; } else { @@ -292,7 +293,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath } NSMutableArray *actions = NSMutableArray.new; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email") detail:nil image:nil style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email") detail:nil image:nil style:UIAlertActionStyleDefault actionHandler:^{ MEGAInviteContactRequestDelegate *inviteContactRequestDelegate = [MEGAInviteContactRequestDelegate.alloc initWithNumberOfRequests:1]; [MEGASdkManager.sharedMEGASdk inviteContactWithEmail:userEmailSelected message:@"" action:MEGAInviteActionAdd delegate:inviteContactRequestDelegate]; }]]; @@ -317,7 +318,7 @@ - (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPat [self updatePromptTitle]; NSUInteger selectedUsersCount = self.selectedUsersMutableArray.count; - self.addBarButtonItem.title = (selectedUsersCount > 1) ? NSLocalizedString(@"addContacts", @"Button title shown in empty views when you can 'Add contacts'") : NSLocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); + self.addBarButtonItem.title = (selectedUsersCount > 1) ? LocalizedString(@"addContacts", @"Button title shown in empty views when you can 'Add contacts'") : LocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); self.addBarButtonItem.enabled = (selectedUsersCount == 0) ? NO : YES; } } diff --git a/iMEGA/Chat/ChatAttachedNodesViewController.h b/iMEGA/Chat/ChatAttachedNodesViewController.h index f30a355494..1aea298ca7 100644 --- a/iMEGA/Chat/ChatAttachedNodesViewController.h +++ b/iMEGA/Chat/ChatAttachedNodesViewController.h @@ -1,4 +1,3 @@ - @class MEGAChatMessage; @interface ChatAttachedNodesViewController : UIViewController diff --git a/iMEGA/Chat/ChatAttachedNodesViewController.m b/iMEGA/Chat/ChatAttachedNodesViewController.m index 21605942b4..da9cc1d920 100644 --- a/iMEGA/Chat/ChatAttachedNodesViewController.m +++ b/iMEGA/Chat/ChatAttachedNodesViewController.m @@ -1,4 +1,3 @@ - #import "ChatAttachedNodesViewController.h" #import "SVProgressHUD.h" @@ -19,6 +18,8 @@ #import "UIImageView+MNZCategory.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @interface ChatAttachedNodesViewController () @property (weak, nonatomic) IBOutlet UITableView *tableView; @@ -66,12 +67,12 @@ - (void)setupAttachedNodes { self.backBarButtonItem.image = self.backBarButtonItem.image.imageFlippedForRightToLeftLayoutDirection; self.navigationItem.leftBarButtonItem = self.backBarButtonItem; - self.editBarButtonItem.title = NSLocalizedString(@"select", @"Caption of a button to select files"); + self.editBarButtonItem.title = LocalizedString(@"select", @"Caption of a button to select files"); self.navigationItem.rightBarButtonItems = @[self.editBarButtonItem]; - self.downloadBarButtonItem.title = NSLocalizedString(@"downloadToOffline", @"List option shown on the details of a file or folder"); + self.downloadBarButtonItem.title = LocalizedString(@"downloadToOffline", @"List option shown on the details of a file or folder"); [self.downloadBarButtonItem setTitleTextAttributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName:[UIColor mnz_redForTraitCollection:self.traitCollection]} forState:UIControlStateNormal]; - self.importBarButtonItem.title = NSLocalizedString(@"Import to Cloud Drive", @"Button title that triggers the importing link action"); + self.importBarButtonItem.title = LocalizedString(@"Import to Cloud Drive", @"Button title that triggers the importing link action"); [self.importBarButtonItem setTitleTextAttributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleBody weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_redForTraitCollection:self.traitCollection]} forState:UIControlStateNormal]; UIBarButtonItem *flexibleItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; @@ -105,43 +106,27 @@ - (void)setNavigationBarButtonItemsEnabled:(BOOL)boolValue { - (void)setNavigationBarTitle { [self updatePromptTitle]; - NSString *navigationTitle = NSLocalizedString(@"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."); + NSString *navigationTitle = LocalizedString(@"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."); navigationTitle = [navigationTitle stringByReplacingOccurrencesOfString:@"%s" withString:self.message.nodeList.size.stringValue]; self.navigationItem.title = navigationTitle; } - (void)updatePromptTitle { if (self.tableView.isEditing) { - NSNumber *selectedNodesCount = [NSNumber numberWithUnsignedInteger:self.selectedNodesMutableArray.count]; - self.navigationItem.prompt = [self titleForPromptWithCountOfNodes:selectedNodesCount]; + self.navigationItem.prompt = [self nodeCountTitle:self.selectedNodesMutableArray.count]; } else { self.navigationItem.prompt = nil; } } -- (NSString *)titleForPromptWithCountOfNodes:(NSNumber *)count { - NSString *promptString; - if (count.unsignedIntegerValue == 0) { - promptString = NSLocalizedString(@"select", @"Button that allows you to select a given folder"); - } else if (count.unsignedIntegerValue == 1) { - promptString = NSLocalizedString(@"oneItemSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected one photo"); - promptString = [promptString stringByReplacingOccurrencesOfString:@"%lu" withString:count.stringValue]; - } else { - promptString = NSLocalizedString(@"itemsSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected more than one photo"); - promptString = [promptString stringByReplacingOccurrencesOfString:@"%lu" withString:count.stringValue]; - } - - return promptString; -} - - (void)setTableViewEditing:(BOOL)editing animated:(BOOL)animated { [self.tableView setEditing:editing animated:animated]; if (editing) { - self.editBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.editBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); [self setToolbarItemsEnabled:NO]; } else { - self.editBarButtonItem.title = NSLocalizedString(@"select", @"Caption of a button to select files"); + self.editBarButtonItem.title = LocalizedString(@"select", @"Caption of a button to select files"); [self setToolbarItemsEnabled:YES]; [self.selectedNodesMutableArray removeAllObjects]; diff --git a/iMEGA/Chat/ChatNotificationControl.swift b/iMEGA/Chat/ChatNotificationControl.swift index ff97c779ce..6812b8bd06 100644 --- a/iMEGA/Chat/ChatNotificationControl.swift +++ b/iMEGA/Chat/ChatNotificationControl.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import UIKit @objc protocol ChatNotificationControlCellProtocol { diff --git a/iMEGA/Chat/ChatRoomCell.m b/iMEGA/Chat/ChatRoomCell.m index 7ddc036476..b185d4f40f 100644 --- a/iMEGA/Chat/ChatRoomCell.m +++ b/iMEGA/Chat/ChatRoomCell.m @@ -12,6 +12,8 @@ #import "UIImageView+MNZCategory.h" #import "MEGAReachabilityManager.h" +@import MEGAL10nObjc; + @interface ChatRoomCell () @property (strong, nonatomic) NSTimer *timer; @@ -76,7 +78,7 @@ - (void)updateAppearance { self.chatLastTime.textColor = [UIColor mnz_subtitlesForTraitCollection:self.traitCollection]; - BOOL chatRoomsTypeArchived = [self.unreadCount.text isEqualToString:NSLocalizedString(@"archived", @"Title of flag of archived chats.")]; + BOOL chatRoomsTypeArchived = [self.unreadCount.text isEqualToString:LocalizedString(@"archived", @"Title of flag of archived chats.")]; self.unreadView.backgroundColor = chatRoomsTypeArchived ? [UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection] : [UIColor mnz_redForTraitCollection:self.traitCollection]; self.unreadCount.textColor = UIColor.whiteColor; @@ -117,7 +119,7 @@ - (void)configureCellForArchivedChat { self.unreadView.hidden = NO; self.unreadView.backgroundColor = [UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection]; self.unreadCount.font = [UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium]; - self.unreadCount.text = NSLocalizedString(@"archived", @"Title of flag of archived chats."); + self.unreadCount.text = LocalizedString(@"archived", @"Title of flag of archived chats."); self.unreadCountLabelHorizontalMarginConstraint.constant = 7; self.activeCallImageView.hidden = YES; } @@ -153,7 +155,7 @@ - (void)configureCellForChatListItem:(MEGAChatListItem *)chatListItem isMuted:(B switch (call.status) { case MEGAChatCallStatusInProgress: self.onCallDuration.hidden = NO; - self.chatLastMessage.text = NSLocalizedString(@"Ongoing Call", @"Text to inform the user there is an active call and is not participating"); + self.chatLastMessage.text = LocalizedString(@"Ongoing Call", @"Text to inform the user there is an active call and is not participating"); self.chatLastMessage.text = [self.chatLastMessage.text stringByAppendingString:@" •"]; self.activeCallImageView.hidden = NO; break; @@ -161,9 +163,9 @@ - (void)configureCellForChatListItem:(MEGAChatListItem *)chatListItem isMuted:(B case MEGAChatCallStatusUserNoPresent: self.activeCallImageView.hidden = NO; if (call.isRinging) { - self.chatLastMessage.text = NSLocalizedString(@"Incoming call", @"notification subtitle of incoming calls"); + self.chatLastMessage.text = LocalizedString(@"Incoming call", @"notification subtitle of incoming calls"); } else { - self.chatLastMessage.text = NSLocalizedString(@"Ongoing Call", @"Text to inform the user there is an active call and is not participating"); + self.chatLastMessage.text = LocalizedString(@"Ongoing Call", @"Text to inform the user there is an active call and is not participating"); } break; @@ -171,7 +173,7 @@ - (void)configureCellForChatListItem:(MEGAChatListItem *)chatListItem isMuted:(B case MEGAChatCallStatusConnecting: case MEGAChatCallStatusJoining: if (!call.isRinging) { - self.chatLastMessage.text = NSLocalizedString(@"calling...", @"Label shown when you call someone (outgoing call), before the call starts."); + self.chatLastMessage.text = LocalizedString(@"calling...", @"Label shown when you call someone (outgoing call), before the call starts."); } break; @@ -200,7 +202,7 @@ - (void)configureCellForUser:(MEGAUser *)user { self.privateChatImageView.hidden = YES; self.chatTitle.text = user.mnz_displayName; - self.chatLastMessage.text = NSLocalizedString(@"noConversationHistory", @"Information if there are no history messages in current chat conversation"); + self.chatLastMessage.text = LocalizedString(@"noConversationHistory", @"Information if there are no history messages in current chat conversation"); [self.avatarView.avatarImageView mnz_setImageForUserHandle:user.handle name:[user mnz_fullName]]; [self.avatarView configureWithMode:MegaAvatarViewModeSingle]; @@ -247,13 +249,13 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { switch (item.lastMessageType) { case 255: { - self.chatLastMessage.text = NSLocalizedString(@"loading", @"state previous to import a file"); + self.chatLastMessage.text = LocalizedString(@"loading", @"state previous to import a file"); self.chatLastTime.hidden = YES; break; } case MEGAChatMessageTypeInvalid: { - self.chatLastMessage.text = NSLocalizedString(@"noConversationHistory", @"Information if there are no history messages in current chat conversation"); + self.chatLastMessage.text = LocalizedString(@"noConversationHistory", @"Information if there are no history messages in current chat conversation"); self.chatLastTime.hidden = YES; break; } @@ -266,10 +268,10 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { NSString *lastMessageString = item.lastMessage; NSArray *componentsArray = [lastMessageString componentsSeparatedByString:@"\x01"]; if (componentsArray.count == 1) { - NSString *attachedFileString = NSLocalizedString(@"attachedFile", @"A message appearing in the chat summary window when the most recent action performed by a user was attaching a file. Please keep %s as it will be replaced at runtime with the name of the attached file."); + NSString *attachedFileString = LocalizedString(@"attachedFile", @"A message appearing in the chat summary window when the most recent action performed by a user was attaching a file. Please keep %s as it will be replaced at runtime with the name of the attached file."); lastMessageString = [attachedFileString stringByReplacingOccurrencesOfString:@"%s" withString:lastMessageString]; } else { - lastMessageString = NSLocalizedString(@"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 = LocalizedString(@"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:@"%tu", componentsArray.count]]; } self.chatLastMessage.text = senderString ? [NSString stringWithFormat:@"%@: %@",senderString, lastMessageString] : lastMessageString; @@ -289,7 +291,7 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { NSTimeInterval duration = node.duration > 0 ? node.duration : 0; durationString = [NSString mnz_stringFromTimeInterval:duration]; } else { - durationString = NSLocalizedString(@"00:00", nil); + durationString = LocalizedString(@"00:00", @""); } NSMutableAttributedString *lastMessageMutableAttributedString = senderString ? [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@: ", senderString]].mutableCopy : [[NSAttributedString alloc] initWithString:@""].mutableCopy; @@ -311,10 +313,10 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { NSString *lastMessageString = item.lastMessage; NSArray *componentsArray = [lastMessageString componentsSeparatedByString:@"\x01"]; if (componentsArray.count == 1) { - NSString *sentContactString = NSLocalizedString(@"sentContact", @"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."); + NSString *sentContactString = LocalizedString(@"sentContact", @"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 = [sentContactString stringByReplacingOccurrencesOfString:@"%s" withString:lastMessageString]; } else { - lastMessageString = NSLocalizedString(@"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 = LocalizedString(@"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:@"%tu", componentsArray.count]]; } self.chatLastMessage.text = senderString ? [NSString stringWithFormat:@"%@: %@",senderString, lastMessageString] : lastMessageString; @@ -323,7 +325,7 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { case MEGAChatMessageTypeTruncate: { NSString *senderString = [self actionAuthorNameInChatListItem:item pronoumForMe:NO]; - NSString *lastMessageString = NSLocalizedString(@"clearedTheChatHistory", @"A log message in the chat conversation to tell the reader that a participant [A] cleared the history of the chat. For example, Alice cleared the chat history."); + NSString *lastMessageString = LocalizedString(@"clearedTheChatHistory", @"A log message in the chat conversation to tell the reader that a participant [A] cleared the history of the chat. For example, Alice cleared the chat history."); lastMessageString = [lastMessageString stringByReplacingOccurrencesOfString:@"[A]" withString:senderString]; self.chatLastMessage.text = lastMessageString; break; @@ -342,15 +344,15 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { NSString *wasChangedToBy; switch (item.lastMessagePriv) { case 0: - wasChangedToBy = NSLocalizedString(@"chat.message.changedRole.readOnly", @"A log message in a chat to display that a participant's permission was changed to read-only and by whom"); + wasChangedToBy = LocalizedString(@"chat.message.changedRole.readOnly", @"A log message in a chat to display that a participant's permission was changed to read-only and by whom"); break; case 2: - wasChangedToBy = NSLocalizedString(@"chat.message.changedRole.standard", @"A log message in a chat to display that a participant's permission was changed to standard role and by whom"); + wasChangedToBy = LocalizedString(@"chat.message.changedRole.standard", @"A log message in a chat to display that a participant's permission was changed to standard role and by whom"); break; case 3: - wasChangedToBy = NSLocalizedString(@"chat.message.changedRole.host", @"A log message in a chat to display that a participant's permission was changed to host role and by whom"); + wasChangedToBy = LocalizedString(@"chat.message.changedRole.host", @"A log message in a chat to display that a participant's permission was changed to host role and by whom"); break; default: @@ -376,12 +378,12 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { switch (item.lastMessagePriv) { case -1: { if (fullNameDidAction && ![fullNameReceiveAction isEqualToString:fullNameDidAction]) { - NSString *wasRemovedFromTheGroupChatBy = NSLocalizedString(@"wasRemovedFromTheGroupChatBy", @"A log message in a chat conversation to tell the reader that a participant [A] was removed from the group chat by the moderator [B]. Please keep [A] and [B], they will be replaced by the participant and the moderator names at runtime. For example: Alice was removed from the group chat by Frank."); + NSString *wasRemovedFromTheGroupChatBy = LocalizedString(@"wasRemovedFromTheGroupChatBy", @"A log message in a chat conversation to tell the reader that a participant [A] was removed from the group chat by the moderator [B]. Please keep [A] and [B], they will be replaced by the participant and the moderator names at runtime. For example: Alice was removed from the group chat by Frank."); wasRemovedFromTheGroupChatBy = [wasRemovedFromTheGroupChatBy stringByReplacingOccurrencesOfString:@"[A]" withString:fullNameReceiveAction]; wasRemovedFromTheGroupChatBy = [wasRemovedFromTheGroupChatBy stringByReplacingOccurrencesOfString:@"[B]" withString:fullNameDidAction]; self.chatLastMessage.text = wasRemovedFromTheGroupChatBy; } else { - NSString *leftTheGroupChat = NSLocalizedString(@"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."); + NSString *leftTheGroupChat = LocalizedString(@"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]; self.chatLastMessage.text = leftTheGroupChat; } @@ -390,12 +392,12 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { case -2: { if (fullNameDidAction && ![fullNameReceiveAction isEqualToString:fullNameDidAction]) { - NSString *joinedTheGroupChatByInvitationFrom = NSLocalizedString(@"joinedTheGroupChatByInvitationFrom", @"A log message in a chat conversation to tell the reader that a participant [A] was added to the chat by a moderator [B]. Please keep the [A] and [B] placeholders, they will be replaced by the participant and the moderator names at runtime. For example: Alice joined the group chat by invitation from Frank."); + NSString *joinedTheGroupChatByInvitationFrom = LocalizedString(@"joinedTheGroupChatByInvitationFrom", @"A log message in a chat conversation to tell the reader that a participant [A] was added to the chat by a moderator [B]. Please keep the [A] and [B] placeholders, they will be replaced by the participant and the moderator names at runtime. For example: Alice joined the group chat by invitation from Frank."); joinedTheGroupChatByInvitationFrom = [joinedTheGroupChatByInvitationFrom stringByReplacingOccurrencesOfString:@"[A]" withString:fullNameReceiveAction]; joinedTheGroupChatByInvitationFrom = [joinedTheGroupChatByInvitationFrom stringByReplacingOccurrencesOfString:@"[B]" withString:fullNameDidAction]; self.chatLastMessage.text = joinedTheGroupChatByInvitationFrom; } else { - NSString *joinedTheGroupChat = [NSString stringWithFormat:NSLocalizedString(@"%@ joined the group chat.", @"Management message shown in a chat when the user %@ joined it from a public chat link"), fullNameReceiveAction]; + NSString *joinedTheGroupChat = [NSString stringWithFormat:LocalizedString(@"%@ joined the group chat.", @"Management message shown in a chat when the user %@ joined it from a public chat link"), fullNameReceiveAction]; self.chatLastMessage.text = joinedTheGroupChat; } break; @@ -412,12 +414,12 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { NSString *fullNameDidAction = [self actionAuthorNameInChatListItem:item pronoumForMe:NO]; MEGAChatRoom *chatRoom = [MEGASdkManager.sharedMEGAChatSdk chatRoomForChatId:item.chatId]; if (chatRoom.retentionTime <= 0) { - text = NSLocalizedString(@"[A]%1$s[/A][B] disabled message clearing.[/B]", @"System message that is shown to all chat participants upon disabling the Retention history."); + text = LocalizedString(@"[A]%1$s[/A][B] disabled message clearing.[/B]", @"System message that is shown to all chat participants upon disabling the Retention history."); text = text.mnz_removeWebclientFormatters; text = [text stringByReplacingOccurrencesOfString:@"%1$s" withString:fullNameDidAction]; } else { - text = NSLocalizedString(@"[A]%1$s[/A][B] changed the message clearing time to[/B][A] %2$s[/A][B].[/B]", @"System message displayed to all chat participants when one of them enables retention history"); + text = LocalizedString(@"[A]%1$s[/A][B] changed the message clearing time to[/B][A] %2$s[/A][B].[/B]", @"System message displayed to all chat participants when one of them enables retention history"); text = text.mnz_removeWebclientFormatters; text = [text stringByReplacingOccurrencesOfString:@"%1$s" withString:fullNameDidAction]; @@ -433,7 +435,7 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { case MEGAChatMessageTypeChatTitle: { NSString *senderString = [self actionAuthorNameInChatListItem:item pronoumForMe:NO]; - NSString *changedGroupChatNameTo = NSLocalizedString(@"changedGroupChatNameTo", @"A hint message in a group chat to indicate the group chat name is changed to a new one. Please keep %s when translating this string which will be replaced with the name at runtime."); + NSString *changedGroupChatNameTo = LocalizedString(@"changedGroupChatNameTo", @"A hint message in a group chat to indicate the group chat name is changed to a new one. Please keep %s when translating this string which will be replaced with the name at runtime."); changedGroupChatNameTo = [changedGroupChatNameTo stringByReplacingOccurrencesOfString:@"[A]" withString:senderString]; changedGroupChatNameTo = [changedGroupChatNameTo stringByReplacingOccurrencesOfString:@"[B]" withString:(item.lastMessage ? item.lastMessage : @" ")]; self.chatLastMessage.text = changedGroupChatNameTo; @@ -452,34 +454,34 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { } case MEGAChatMessageTypeCallStarted: { - self.chatLastMessage.text = NSLocalizedString(@"Ongoing Call", @"Text to inform the user there is an active call and is not participating"); + self.chatLastMessage.text = LocalizedString(@"Ongoing Call", @"Text to inform the user there is an active call and is not participating"); break; } case MEGAChatMessageTypePublicHandleCreate: { NSString *senderString = [self actionAuthorNameInChatListItem:item pronoumForMe:NO]; - NSString *publicHandleCreated = [NSString stringWithFormat:NSLocalizedString(@"%@ created a public link for the chat.", @"Management message shown in a chat when the user %@ creates a public link for the chat"), senderString]; + NSString *publicHandleCreated = [NSString stringWithFormat:LocalizedString(@"%@ created a public link for the chat.", @"Management message shown in a chat when the user %@ creates a public link for the chat"), senderString]; self.chatLastMessage.text = publicHandleCreated; break; } case MEGAChatMessageTypePublicHandleDelete: { NSString *senderString = [self actionAuthorNameInChatListItem:item pronoumForMe:NO]; - NSString *publicHandleRemoved = [NSString stringWithFormat:NSLocalizedString(@"%@ removed a public link for the chat.", @"Management message shown in a chat when the user %@ removes a public link for the chat"), senderString]; + NSString *publicHandleRemoved = [NSString stringWithFormat:LocalizedString(@"%@ removed a public link for the chat.", @"Management message shown in a chat when the user %@ removes a public link for the chat"), senderString]; self.chatLastMessage.text = publicHandleRemoved; break; } case MEGAChatMessageTypeSetPrivateMode: { NSString *senderString = [self actionAuthorNameInChatListItem:item pronoumForMe:NO]; - NSString *setPrivateMode = [NSString stringWithFormat:NSLocalizedString(@"%@ enabled Encrypted Key Rotation", @"Management message shown in a chat when the user %@ enables the 'Encrypted Key Rotation'"), senderString]; + NSString *setPrivateMode = [NSString stringWithFormat:LocalizedString(@"%@ enabled Encrypted Key Rotation", @"Management message shown in a chat when the user %@ enables the 'Encrypted Key Rotation'"), senderString]; self.chatLastMessage.text = setPrivateMode; break; } case MEGAChatMessageTypeScheduledMeeting: { NSString *senderString = [self actionAuthorNameInChatListItem:item pronoumForMe:NO]; - NSString *meetingCancelledString = [NSString stringWithFormat:NSLocalizedString(@"meetings.scheduled.managementMessages.updated", @"A log message in the chat conversation to tell the reader that a participant [A] cancelled the meeting. For example: Zadie Smith cancelled this meeting "), senderString]; + NSString *meetingCancelledString = [NSString stringWithFormat:LocalizedString(@"meetings.scheduled.managementMessages.updated", @"A log message in the chat conversation to tell the reader that a participant [A] cancelled the meeting. For example: Zadie Smith cancelled this meeting "), senderString]; self.chatLastMessage.text = meetingCancelledString; break; } @@ -496,7 +498,7 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { NSString *locationMessageImageName = self.chatListItem.unreadCount ? @"locationMessage" : @"locationMessageGrey"; NSAttributedString *pinImageAttributedString = [NSAttributedString mnz_attributedStringFromImageNamed:locationMessageImageName fontCapHeight:self.chatLastMessage.font.capHeight]; [lastMessageMutableAttributedString appendAttributedString:pinImageAttributedString]; - [lastMessageMutableAttributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedString(@"Pinned Location", @"Text shown in location-type messages")]]; + [lastMessageMutableAttributedString appendAttributedString:[[NSAttributedString alloc] initWithString:LocalizedString(@"Pinned Location", @"Text shown in location-type messages")]]; self.chatLastMessage.attributedText = lastMessageMutableAttributedString; @@ -516,7 +518,7 @@ - (void)updateLastMessageForChatListItem:(MEGAChatListItem *)item { - (NSString *)actionAuthorNameInChatListItem:(MEGAChatListItem *)item pronoumForMe:(BOOL)me { NSString *actionAuthor; if (item.lastMessageSender == [[MEGASdkManager sharedMEGAChatSdk] myUserHandle]) { - actionAuthor = me ? NSLocalizedString(@"me", @"The title for my message in a chat. The message was sent from yourself.") : MEGASdkManager.sharedMEGAChatSdk.myFullname; + actionAuthor = me ? LocalizedString(@"me", @"The title for my message in a chat. The message was sent from yourself.") : MEGASdkManager.sharedMEGAChatSdk.myFullname; } else { MEGAChatRoom *chatRoom = [MEGASdkManager.sharedMEGAChatSdk chatRoomForChatId:item.chatId]; actionAuthor = [chatRoom userDisplayNameForUserHandle:item.lastMessageSender]; diff --git a/iMEGA/Chat/ChatRoomsViewController.m b/iMEGA/Chat/ChatRoomsViewController.m index 4739c89476..c40b0b2719 100644 --- a/iMEGA/Chat/ChatRoomsViewController.m +++ b/iMEGA/Chat/ChatRoomsViewController.m @@ -22,6 +22,7 @@ #import "TransfersWidgetViewController.h" #import "NSArray+MNZCategory.h" +@import MEGAL10nObjc; @import MEGAUIKit; @interface ChatRoomsViewController () @@ -97,8 +98,8 @@ - (void)viewDidLoad { [self.usersWithoutChatArray addObject:user]; } } - self.addBarButtonItem.accessibilityLabel = NSLocalizedString(@"startConversation", comment: "start a chat/conversation"); - self.moreBarButtonItem.accessibilityLabel = NSLocalizedString(@"more", @"Top menu option which opens more menu options in a context menu."); + self.addBarButtonItem.accessibilityLabel = LocalizedString(@"startConversation", @"start a chat/conversation"); + self.moreBarButtonItem.accessibilityLabel = LocalizedString(@"more", @"Top menu option which opens more menu options in a context menu."); switch (self.chatRoomsType) { case ChatRoomsTypeDefault: @@ -239,12 +240,12 @@ - (void)emptyDataSetWillAppear:(UIScrollView *)scrollView { self.searchController.searchBar.hidden = YES; self.archivedChatListItemList = MEGASdkManager.sharedMEGAChatSdk.archivedChatListItems; if (self.archivedChatListItemList.size) { - self.archivedChatEmptyStateTitle.text = NSLocalizedString(@"archivedChats", @"Title of archived chats button"); + self.archivedChatEmptyStateTitle.text = LocalizedString(@"archivedChats", @"Title of archived chats button"); self.archivedChatEmptyStateCount.text = [NSString stringWithFormat:@"%tu", self.archivedChatListItemList.size]; self.archivedChatEmptyState.hidden = NO; } if (self.chatRoomsType == ChatRoomsTypeDefault) { - self.contactsOnMegaEmptyStateTitle.text = NSLocalizedString(@"Invite contact now", @"Text emncouraging the user to add contacts in MEGA"); + self.contactsOnMegaEmptyStateTitle.text = LocalizedString(@"Invite contact now", @"Text emncouraging the user to add contacts in MEGA"); self.contactsOnMegaEmptyStateView.hidden = NO; } } @@ -268,21 +269,21 @@ - (NSString *)titleForEmptyState { NSString *text = @""; if (self.searchController.isActive) { if (self.searchController.searchBar.text.length > 0) { - text = NSLocalizedString(@"noResults", @"Title shown when you make a search and there is 'No Results'"); + text = LocalizedString(@"noResults", @"Title shown when you make a search and there is 'No Results'"); } } else { switch (self.chatRoomsType) { case ChatRoomsTypeDefault: if (self.chatSelectorButton.isSelected) { - text = NSLocalizedString(@"chat.chats.emptyState.title", @"Ttile for empty chats tab"); + text = LocalizedString(@"chat.chats.emptyState.title", @"Ttile for empty chats tab"); } else { - text = NSLocalizedString(@"chat.meetings.emptyState.title", @"Ttile for empty meetings tab "); + text = LocalizedString(@"chat.meetings.emptyState.title", @"Ttile for empty meetings tab "); } break; case ChatRoomsTypeArchived: - text = NSLocalizedString(@"noArchivedChats", @"Title of empty state view for archived chats."); + text = LocalizedString(@"noArchivedChats", @"Title of empty state view for archived chats."); break; } } @@ -299,9 +300,9 @@ - (NSString *)descriptionForEmptyState { switch (self.chatRoomsType) { case ChatRoomsTypeDefault: if (self.chatSelectorButton.isSelected) { - text = NSLocalizedString(@"chat.chats.emptyState.description", @"Description for empty chats tab"); + text = LocalizedString(@"chat.chats.emptyState.description", @"Description for empty chats tab"); } else { - text = NSLocalizedString(@"chat.meetings.emptyState.description", @"Description for empty meetings tab"); + text = LocalizedString(@"chat.meetings.emptyState.description", @"Description for empty meetings tab"); } break; @@ -350,9 +351,9 @@ - (NSString *)buttonTitleForEmptyState { switch (self.chatRoomsType) { case ChatRoomsTypeDefault: if (self.chatSelectorButton.isSelected) { - text = NSLocalizedString(@"chat.chats.emptyState.button.title", @"Text button for empty chats tab"); + text = LocalizedString(@"chat.chats.emptyState.button.title", @"Text button for empty chats tab"); } else { - text = NSLocalizedString(@"chat.meetings.emptyState.button.title", @"Text button for empty meetings tab"); + text = LocalizedString(@"chat.meetings.emptyState.button.title", @"Text button for empty meetings tab"); } break; case ChatRoomsTypeArchived: @@ -680,7 +681,7 @@ - (UITableViewCell *)userCellForIndexPath:(NSIndexPath *)indexPath { - (UITableViewCell *)contactsOnMegaCellForIndexPath:(NSIndexPath *)indexPath { ChatRoomCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"contactsOnMegaCell" forIndexPath:indexPath]; - cell.chatTitle.text = NSLocalizedString(@"Invite contact now", @"Text emncouraging the user to add contacts in MEGA"); + cell.chatTitle.text = LocalizedString(@"Invite contact now", @"Text emncouraging the user to add contacts in MEGA"); return cell; } @@ -689,7 +690,7 @@ - (UITableViewCell *)archivedChatsCellForIndexPath:(NSIndexPath *)indexPath { cell.avatarView.avatarImageView.image = [UIImage imageNamed:@"archiveChat"]; cell.avatarView.avatarImageView.tintColor = [UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]; [cell.avatarView configureWithMode:MegaAvatarViewModeSingle]; - cell.chatTitle.text = NSLocalizedString(@"archivedChats", @"Title of archived chats button"); + cell.chatTitle.text = LocalizedString(@"archivedChats", @"Title of archived chats button"); cell.chatLastMessage.text = [NSString stringWithFormat:@"%tu", self.archivedChatListItemList.size]; return cell; } @@ -716,30 +717,30 @@ - (void)showOptionsForChatAtIndexPath:(NSIndexPath *)indexPath { NSMutableArray *actions = NSMutableArray.new; if (chatListItem.unreadCount != 0) { - ActionSheetAction *markAsReadAction = [ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"Mark as Read", @"A button label. The button allows the user to mark a conversation as read.)") detail:nil accessoryView:nil image:[UIImage imageNamed:@"markUnread_menu"] style:UIAlertActionStyleDefault actionHandler:^{ + ActionSheetAction *markAsReadAction = [ActionSheetAction.alloc initWithTitle:LocalizedString(@"Mark as Read", @"A button label. The button allows the user to mark a conversation as read.)") detail:nil accessoryView:nil image:[UIImage imageNamed:@"markUnread_menu"] style:UIAlertActionStyleDefault actionHandler:^{ [MEGASdkManager.sharedMEGAChatSdk setMessageSeenForChat:chatListItem.chatId messageId:chatListItem.lastMessageId]; }]; [actions addObject:markAsReadAction]; } if ([self.chatNotificationControl isChatDNDEnabledWithChatId:chatListItem.chatId]) { - ActionSheetAction *unmuteAction = [ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"unmute", @"A button label. The button allows the user to unmute a conversation") detail:nil accessoryView:nil image:[UIImage imageNamed:@"mutedChat_menu"] style:UIAlertActionStyleDefault actionHandler:^{ + ActionSheetAction *unmuteAction = [ActionSheetAction.alloc initWithTitle:LocalizedString(@"unmute", @"A button label. The button allows the user to unmute a conversation") detail:nil accessoryView:nil image:[UIImage imageNamed:@"mutedChat_menu"] style:UIAlertActionStyleDefault actionHandler:^{ [self.chatNotificationControl turnOffDNDWithChatId:chatListItem.chatId]; }]; [actions addObject:unmuteAction]; } else { - ActionSheetAction *muteAction = [ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"mute", @"A button label. The button allows the user to mute a conversation") detail:nil accessoryView:nil image:[UIImage imageNamed:@"mutedChat_menu"] style:UIAlertActionStyleDefault actionHandler:^{ + ActionSheetAction *muteAction = [ActionSheetAction.alloc initWithTitle:LocalizedString(@"mute", @"A button label. The button allows the user to mute a conversation") detail:nil accessoryView:nil image:[UIImage imageNamed:@"mutedChat_menu"] style:UIAlertActionStyleDefault actionHandler:^{ [self.chatNotificationControl turnOnDNDWithChatId:chatListItem.chatId isChatTypeMeeting:[self isChatTypeMeeting] sender:[self.tableView cellForRowAtIndexPath:indexPath]]; }]; [actions addObject:muteAction]; } - ActionSheetAction *infoAction = [ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"info", @"A button label. The button allows the user to get more info of the current context.") detail:nil accessoryView:nil image:[UIImage imageNamed:@"info"] style:UIAlertActionStyleDefault actionHandler:^{ + ActionSheetAction *infoAction = [ActionSheetAction.alloc initWithTitle:LocalizedString(@"info", @"A button label. The button allows the user to get more info of the current context.") detail:nil accessoryView:nil image:[UIImage imageNamed:@"info"] style:UIAlertActionStyleDefault actionHandler:^{ [self presentGroupOrContactDetailsForChatListItem:chatListItem]; }]; [actions addObject:infoAction]; - ActionSheetAction *archiveChatAction = [ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"archiveChat", @"Title of button to archive chats.)") detail:nil accessoryView:nil image:[UIImage imageNamed:@"archiveChat_menu"] style:UIAlertActionStyleDefault actionHandler:^{ + ActionSheetAction *archiveChatAction = [ActionSheetAction.alloc initWithTitle:LocalizedString(@"archiveChat", @"Title of button to archive chats.)") detail:nil accessoryView:nil image:[UIImage imageNamed:@"archiveChat_menu"] style:UIAlertActionStyleDefault actionHandler:^{ [MEGASdkManager.sharedMEGAChatSdk archiveChat:chatListItem.chatId archive:YES]; }]; [actions addObject:archiveChatAction]; @@ -791,7 +792,7 @@ - (void)initTimerForCall:(MEGAChatCall *)call { - (void)updateDuration { if (!self.isReconnecting) { NSTimeInterval interval = ([NSDate date].timeIntervalSince1970 - self.baseDate.timeIntervalSince1970 + self.initDuration); - NSString *title = [NSString stringWithFormat:NSLocalizedString(@"chat.callInProgress.tapToReturnToCall", @"Message shown in a chat room for a call in progress displaying the duration of the call"), [NSString mnz_stringFromTimeInterval:interval]]; + NSString *title = [NSString stringWithFormat:LocalizedString(@"chat.callInProgress.tapToReturnToCall", @"Message shown in a chat room for a call in progress displaying the duration of the call"), [NSString mnz_stringFromTimeInterval:interval]]; self.topBannerLabel.text = title; [self manageCallIndicators]; @@ -811,7 +812,7 @@ - (void)manageCallIndicators { - (void)configureTopBannerForInProgressCall:(MEGAChatCall *)call { if (self.isReconnecting) { - self.topBannerLabel.text = NSLocalizedString(@"You are back!", @"Title shown when the user reconnect in a call."); + self.topBannerLabel.text = LocalizedString(@"You are back!", @"Title shown when the user reconnect in a call."); self.topBannerView.backgroundColor = [UIColor mnz_turquoiseForTraitCollection:self.traitCollection]; self.topBannerMicrophoneMutedImageView.hidden = YES; self.topBannerCameraEnabledImageView.hidden = YES; @@ -990,7 +991,7 @@ - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuCo NSMutableArray *menus = [NSMutableArray new]; if (chatRoom.unreadCount != 0) { - UIAction *markAsReadAction = [UIAction actionWithTitle:NSLocalizedString(@"Mark as Read",@"A button label. The button allows the user to mark a conversation as read.") image:[UIImage imageNamed:@"markUnread_menu"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { + UIAction *markAsReadAction = [UIAction actionWithTitle:LocalizedString(@"Mark as Read",@"A button label. The button allows the user to mark a conversation as read.") image:[UIImage imageNamed:@"markUnread_menu"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { [MEGASdkManager.sharedMEGAChatSdk setMessageSeenForChat:chatListItem.chatId messageId:chatListItem.lastMessageId]; }]; [menus addObject:markAsReadAction]; @@ -998,32 +999,32 @@ - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuCo BOOL muted = [self.chatNotificationControl isChatDNDEnabledWithChatId:chatListItem.chatId]; if (muted) { - UIAction *unmuteAction = [UIAction actionWithTitle:NSLocalizedString(@"unmute", @"A button label. The button allows the user to unmute a conversation") image:[UIImage imageNamed:@"mutedChat_menu"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { + UIAction *unmuteAction = [UIAction actionWithTitle:LocalizedString(@"unmute", @"A button label. The button allows the user to unmute a conversation") image:[UIImage imageNamed:@"mutedChat_menu"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { [self.chatNotificationControl turnOffDNDWithChatId:chatListItem.chatId]; }]; [menus addObject:unmuteAction]; } else { - UIAction *muteAction = [UIAction actionWithTitle:NSLocalizedString(@"mute", @"A button label. The button allows the user to mute a conversation") image:[UIImage imageNamed:@"mutedChat_menu"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { + UIAction *muteAction = [UIAction actionWithTitle:LocalizedString(@"mute", @"A button label. The button allows the user to mute a conversation") image:[UIImage imageNamed:@"mutedChat_menu"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { [self.chatNotificationControl turnOnDNDWithChatId:chatListItem.chatId isChatTypeMeeting:[self isChatTypeMeeting] sender:[tableView cellForRowAtIndexPath:indexPath]]; }]; [menus addObject:muteAction]; } - UIAction *infoAction = [UIAction actionWithTitle:NSLocalizedString(@"info", @"A button label. The button allows the user to get more info of the current context. ") image:[UIImage imageNamed:@"info"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { + UIAction *infoAction = [UIAction actionWithTitle:LocalizedString(@"info", @"A button label. The button allows the user to get more info of the current context. ") image:[UIImage imageNamed:@"info"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { [self presentGroupOrContactDetailsForChatListItem:chatListItem]; }]; [menus addObject:infoAction]; switch (self.chatRoomsType) { case ChatRoomsTypeDefault: { - UIAction *archiveChatAction = [UIAction actionWithTitle:NSLocalizedString(@"archiveChat", @"Title of button to archive chats.") image:[UIImage imageNamed:@"archiveChat_menu"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { + UIAction *archiveChatAction = [UIAction actionWithTitle:LocalizedString(@"archiveChat", @"Title of button to archive chats.") image:[UIImage imageNamed:@"archiveChat_menu"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { [MEGASdkManager.sharedMEGAChatSdk archiveChat:chatListItem.chatId archive:YES]; }]; [menus addObject:archiveChatAction]; break; } case ChatRoomsTypeArchived:{ - UIAction *archiveChatAction = [UIAction actionWithTitle:NSLocalizedString(@"unarchiveChat", @"The title of the dialog to unarchive an archived chat.") image:[UIImage imageNamed:@"unArchiveChat"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { + UIAction *archiveChatAction = [UIAction actionWithTitle:LocalizedString(@"unarchiveChat", @"The title of the dialog to unarchive an archived chat.") image:[UIImage imageNamed:@"unArchiveChat"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { [[MEGASdkManager sharedMEGAChatSdk] archiveChat:chatListItem.chatId archive:NO]; }]; [menus addObject:archiveChatAction]; @@ -1342,7 +1343,7 @@ - (void)onChatCallUpdate:(MEGAChatSdk *)api call:(MEGAChatCall *)call { case MEGAChatCallStatusConnecting: self.reconnecting = YES; - self.topBannerLabel.text = NSLocalizedString(@"Reconnecting...", @"Title shown when the user lost the connection in a call, and the app will try to reconnect the user again."); + self.topBannerLabel.text = LocalizedString(@"Reconnecting...", @"Title shown when the user lost the connection in a call, and the app will try to reconnect the user again."); self.topBannerView.backgroundColor = UIColor.systemOrangeColor; self.topBannerMicrophoneMutedImageView.hidden = YES; self.topBannerCameraEnabledImageView.hidden = YES; diff --git a/iMEGA/Chat/Giphy/GiphyPreviewViewController.swift b/iMEGA/Chat/Giphy/GiphyPreviewViewController.swift index 4d8c7f21fe..8fe0b221b7 100644 --- a/iMEGA/Chat/Giphy/GiphyPreviewViewController.swift +++ b/iMEGA/Chat/Giphy/GiphyPreviewViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit class GiphyPreviewViewController: UIViewController { diff --git a/iMEGA/Chat/Giphy/GiphySelectionView.swift b/iMEGA/Chat/Giphy/GiphySelectionView.swift index d815a05eae..06604a524b 100644 --- a/iMEGA/Chat/Giphy/GiphySelectionView.swift +++ b/iMEGA/Chat/Giphy/GiphySelectionView.swift @@ -1,4 +1,5 @@ import FlexLayout +import MEGAL10n import PinLayout import UIKit @@ -193,7 +194,7 @@ extension GiphySelectionView: CHTCollectionViewDelegateWaterfallLayout, UICollec let srcWebp = gif.webp.replacingOccurrences(of: ServiceManager.shared.BASE_URL, with: ServiceManager.shared.GIPHY_URL) let srcMp4 = gif.mp4.replacingOccurrences(of: ServiceManager.shared.BASE_URL, with: ServiceManager.shared.GIPHY_URL) - MEGASdkManager.sharedMEGAChatSdk().sendGiphy(toChat: self.controller.chatRoom.chatId, srcMp4: srcMp4, srcWebp: srcWebp, sizeMp4: sizeMp4, sizeWebp: sizeWebp, width: width, height: height, title: gif.title) + MEGAChatSdk.shared.sendGiphy(toChat: self.controller.chatRoom.chatId, srcMp4: srcMp4, srcWebp: srcWebp, sizeMp4: sizeMp4, sizeWebp: sizeWebp, width: width, height: height, title: gif.title) self.controller.navigationController?.dismiss(animated: true, completion: nil) } controller.navigationController?.pushViewController(previewVC, animated: true) @@ -256,7 +257,7 @@ extension GiphySelectionView: UISearchBarDelegate, UISearchControllerDelegate { extension GiphySelectionView: DZNEmptyDataSetSource, DZNEmptyDataSetDelegate { func title(forEmptyDataSet _: UIScrollView) -> NSAttributedString? { - let title = NSAttributedString(string: NSLocalizedString("No GIFs found", comment: ""), + let title = NSAttributedString(string: Strings.localized("No GIFs found", comment: ""), attributes: [ NSAttributedString.Key.foregroundColor: UIColor.mnz_label().withAlphaComponent(0.5), NSAttributedString.Key.font: UIFont.preferredFont(style: .body, weight: .semibold) diff --git a/iMEGA/Chat/Giphy/GiphySelectionViewController.swift b/iMEGA/Chat/Giphy/GiphySelectionViewController.swift index a3d88ae1af..362824fd0c 100644 --- a/iMEGA/Chat/Giphy/GiphySelectionViewController.swift +++ b/iMEGA/Chat/Giphy/GiphySelectionViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGAUIKit import UIKit @@ -28,13 +29,13 @@ class GiphySelectionViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - title = NSLocalizedString("Send GIF", comment: "") + title = Strings.localized("Send GIF", comment: "") navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil) searchController.obscuresBackgroundDuringPresentation = false searchController.hidesNavigationBarDuringPresentation = false searchController.searchBar.showsCancelButton = false - searchController.searchBar.placeholder = NSLocalizedString("Search GIPHY", comment: "") + searchController.searchBar.placeholder = Strings.localized("Search GIPHY", comment: "") definesPresentationContext = true searchController.searchBar.delegate = mainView diff --git a/iMEGA/Chat/GroupChatDetailsViewController.m b/iMEGA/Chat/GroupChatDetailsViewController.m index 6576dd5250..40bbb5b463 100644 --- a/iMEGA/Chat/GroupChatDetailsViewController.m +++ b/iMEGA/Chat/GroupChatDetailsViewController.m @@ -22,6 +22,8 @@ #import "MEGAStore.h" #import "MEGA-Swift.h" #import "NSArray+MNZCategory.h" + +@import MEGAL10nObjc; @import MEGASDKRepo; @interface GroupChatDetailsViewController () @@ -46,7 +48,7 @@ @implementation GroupChatDetailsViewController - (void)viewDidLoad { [super viewDidLoad]; - NSString *title = NSLocalizedString(@"info", @"A button label. The button allows the user to get more info of the current context"); + NSString *title = LocalizedString(@"info", @"A button label. The button allows the user to get more info of the current context"); self.navigationItem.title = title; [self setMenuCapableBackButtonWithMenuTitle:title]; self.requestedParticipantsMutableSet = NSMutableSet.new; @@ -142,10 +144,10 @@ - (void)updateHeadingView { [self.avatarView setupFor:self.chatRoom]; if (self.chatRoom.ownPrivilege < MEGAChatRoomPrivilegeRo) { - self.participantsLabel.text = NSLocalizedString(@"Inactive chat", @"Subtitle of chat screen when the chat is inactive"); + self.participantsLabel.text = LocalizedString(@"Inactive chat", @"Subtitle of chat screen when the chat is inactive"); } else { NSInteger peers = self.chatRoom.peerCount + (!self.chatRoom.isPreview ? 1 : 0); - self.participantsLabel.text = (peers == 1) ? [NSString stringWithFormat:NSLocalizedString(@"%d participant", @"Singular of participant. 1 participant"), 1] : [NSString stringWithFormat:NSLocalizedString(@"%d participants", @"Singular of participant. 1 participant"), (int)peers]; + self.participantsLabel.text = (peers == 1) ? [NSString stringWithFormat:LocalizedString(@"%d participant", @"Singular of participant. 1 participant"), 1] : [NSString stringWithFormat:LocalizedString(@"%d participants", @"Singular of participant. 1 participant"), (int)peers]; } } @@ -203,11 +205,11 @@ - (void)alertTextFieldDidChange:(UITextField *)textField { } - (void)showArchiveChatAlert { - NSString *title = self.chatRoom.isArchived ? NSLocalizedString(@"unarchiveChatMessage", @"Confirmation message for user to confirm it will unarchive an archived chat.") : NSLocalizedString(@"archiveChatMessage", @"Confirmation message on archive chat dialog for user to confirm."); + NSString *title = self.chatRoom.isArchived ? LocalizedString(@"unarchiveChatMessage", @"Confirmation message for user to confirm it will unarchive an archived chat.") : LocalizedString(@"archiveChatMessage", @"Confirmation message on archive chat dialog for user to confirm."); UIAlertController *archiveAlertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; - [archiveAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [archiveAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - [archiveAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [archiveAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { MEGAArchiveChatRequestDelegate *archiveChatRequesDelegate = [[MEGAArchiveChatRequestDelegate alloc] initWithCompletion:^(MEGAChatRoom *chatRoom) { if (chatRoom.isArchived) { if (self.navigationController.childViewControllers.count >= 3) { @@ -227,10 +229,10 @@ - (void)showArchiveChatAlert { } - (void)showLeaveChatAlert { - UIAlertController *leaveAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"youWillNoLongerHaveAccessToThisConversation", @"Alert text that explains what means confirming the action 'Leave'") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [leaveAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *leaveAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"youWillNoLongerHaveAccessToThisConversation", @"Alert text that explains what means confirming the action 'Leave'") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [leaveAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - [leaveAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"leave", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [leaveAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"leave", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { MEGAChatGenericRequestDelegate *delegate = [MEGAChatGenericRequestDelegate.alloc initWithCompletion:^(MEGAChatRequest * _Nonnull request, MEGAChatError * _Nonnull error) { if (!error.type) { [MEGALinkManager.joiningOrLeavingChatBase64Handles removeObject:[MEGASdk base64HandleForUserHandle:self.chatRoom.chatId]]; @@ -246,9 +248,9 @@ - (void)showLeaveChatAlert { - (void)renameChatGroup { NSString *title = self.chatRoom.isMeeting - ? NSLocalizedString(@"meetings.info.renameMeeting", @"The title of a menu button which allows users to rename a meeting.") - : NSLocalizedString(@"renameGroup", @"The title of a menu button which allows users to rename a group chat."); - UIAlertController *renameGroupAlertController = [UIAlertController alertControllerWithTitle:title message:NSLocalizedString(@"renameNodeMessage", @"Hint text to suggest that the user have to write the new name for the file or folder") preferredStyle:UIAlertControllerStyleAlert]; + ? LocalizedString(@"meetings.info.renameMeeting", @"The title of a menu button which allows users to rename a meeting.") + : LocalizedString(@"renameGroup", @"The title of a menu button which allows users to rename a group chat."); + UIAlertController *renameGroupAlertController = [UIAlertController alertControllerWithTitle:title message:LocalizedString(@"renameNodeMessage", @"Hint text to suggest that the user have to write the new name for the file or folder") preferredStyle:UIAlertControllerStyleAlert]; [renameGroupAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.text = self.chatRoom.title; @@ -258,9 +260,9 @@ - (void)renameChatGroup { }; }]; - UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]; + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]; - UIAlertAction *renameAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"rename", @"Title for the action that allows you to rename a file or folder") style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + UIAlertAction *renameAction = [UIAlertAction actionWithTitle:LocalizedString(@"rename", @"Title for the action that allows you to rename a file or folder") style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { UITextField *textField = [[renameGroupAlertController textFields] firstObject]; NSString *newGroupName = textField.text; [[MEGASdkManager sharedMEGAChatSdk] setChatTitle:self.chatRoom.chatId title:newGroupName delegate:self]; @@ -277,15 +279,15 @@ - (void)renameChatGroup { - (void)presentNoChatLinkAvailable { NSString *descriptionText = nil; if (self.chatRoom.isMeeting) { - descriptionText = NSLocalizedString(@"meetings.sharelink.Error", @""); + descriptionText = LocalizedString(@"meetings.sharelink.Error", @""); } else { - descriptionText = NSLocalizedString(@"No chat link available.", @"In some cases, a user may try to get the link for a chat room, but if such is not set by an operator - it would say \"not link available\" and not auto create it."); + descriptionText = LocalizedString(@"No chat link available.", @"In some cases, a user may try to get the link for a chat room, but if such is not set by an operator - it would say \"not link available\" and not auto create it."); } CustomModalAlertViewController *customModalAlertVC = [[CustomModalAlertViewController alloc] init]; customModalAlertVC.image = [UIImage imageNamed:@"chatLinkCreation"]; customModalAlertVC.viewTitle = self.chatRoom.title; - customModalAlertVC.firstButtonTitle = NSLocalizedString(@"close", @"A button label. The button allows the user to close the conversation."); + customModalAlertVC.firstButtonTitle = LocalizedString(@"close", @"A button label. The button allows the user to close the conversation."); customModalAlertVC.link = descriptionText; __weak typeof(CustomModalAlertViewController) *weakCustom = customModalAlertVC; customModalAlertVC.firstCompletion = ^{ @@ -300,14 +302,14 @@ - (void)presentChatLinkOptionsWithLink:(NSString *)link { customModalAlertVC.image = [UIImage imageNamed:@"chatLinkCreation"]; customModalAlertVC.viewTitle = self.chatRoom.title; customModalAlertVC.detail = self.chatRoom.isMeeting - ? NSLocalizedString(@"meetings.info.shareMeetingLink.explainLink", @"Text explaining users how the meeting links work.") - : NSLocalizedString(@"People can join your group by using this link.", @"Text explaining users how the chat links work."); - customModalAlertVC.firstButtonTitle = NSLocalizedString(@"general.share", @"Button title which, if tapped, will trigger the action of sharing with the contact or contacts selected"); + ? LocalizedString(@"meetings.info.shareMeetingLink.explainLink", @"Text explaining users how the meeting links work.") + : LocalizedString(@"People can join your group by using this link.", @"Text explaining users how the chat links work."); + customModalAlertVC.firstButtonTitle = LocalizedString(@"general.share", @"Button title which, if tapped, will trigger the action of sharing with the contact or contacts selected"); customModalAlertVC.link = link; if (self.chatRoom.ownPrivilege == MEGAChatRoomPrivilegeModerator) { - customModalAlertVC.secondButtonTitle = NSLocalizedString(@"delete", nil); + customModalAlertVC.secondButtonTitle = LocalizedString(@"delete", @""); } - customModalAlertVC.dismissButtonTitle = NSLocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); + customModalAlertVC.dismissButtonTitle = LocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); NSURL *url = [NSURL URLWithString:link]; MeetingLinkPresentationItemSource *itemSource = [[MeetingLinkPresentationItemSource alloc] initWithUrl:url title:self.chatRoom.title]; NSArray *activityItems = self.chatRoom.isMeeting @@ -330,7 +332,7 @@ - (void)presentChatLinkOptionsWithLink:(NSString *)link { [weakCustom dismissViewControllerAnimated:YES completion:^{ MEGAChatGenericRequestDelegate *delegate = [[MEGAChatGenericRequestDelegate alloc] initWithCompletion:^(MEGAChatRequest * _Nonnull request, MEGAChatError * _Nonnull error) { if (!error.type) { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"linkRemoved", @"Message shown when the link to a file or folder has been removed")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"linkRemoved", @"Message shown when the link to a file or folder has been removed")]; } }]; [[MEGASdkManager sharedMEGAChatSdk] removeChatLink:self.chatRoom.chatId delegate:delegate]; @@ -382,20 +384,20 @@ - (IBAction)didTapPermissionsButton:(UIButton *)sender { UIImageView *checkmarkImageView = [UIImageView.alloc initWithImage:[UIImage imageNamed:@"turquoise_checkmark"]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"moderator", @"The Moderator permission level in chat. With moderator permissions a participant can manage the chat.") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeModerator ? checkmarkImageView : nil image:[UIImage imageNamed:@"moderator"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"moderator", @"The Moderator permission level in chat. With moderator permissions a participant can manage the chat.") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeModerator ? checkmarkImageView : nil image:[UIImage imageNamed:@"moderator"] style:UIAlertActionStyleDefault actionHandler:^{ [MEGASdkManager.sharedMEGAChatSdk updateChatPermissions:weakSelf.chatRoom.chatId userHandle:userHandle privilege:MEGAChatRoomPrivilegeModerator delegate:weakSelf]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"standard", @"The Standard permission level in chat. With the standard permissions a participant can read and type messages in a chat.") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeStandard ? checkmarkImageView : nil image:[UIImage imageNamed:@"standard"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"standard", @"The Standard permission level in chat. With the standard permissions a participant can read and type messages in a chat.") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeStandard ? checkmarkImageView : nil image:[UIImage imageNamed:@"standard"] style:UIAlertActionStyleDefault actionHandler:^{ [MEGASdkManager.sharedMEGAChatSdk updateChatPermissions:weakSelf.chatRoom.chatId userHandle:userHandle privilege:MEGAChatRoomPrivilegeStandard delegate:weakSelf]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"readOnly", @"Permissions given to the user you share your folder with") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeRo ? checkmarkImageView : nil image:[UIImage imageNamed:@"readOnly_chat"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"readOnly", @"Permissions given to the user you share your folder with") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeRo ? checkmarkImageView : nil image:[UIImage imageNamed:@"readOnly_chat"] style:UIAlertActionStyleDefault actionHandler:^{ [MEGASdkManager.sharedMEGAChatSdk updateChatPermissions:weakSelf.chatRoom.chatId userHandle:userHandle privilege:MEGAChatRoomPrivilegeRo delegate:weakSelf]; }]]; if (peerEmail) { MEGAUser *user = [MEGASdkManager.sharedMEGASdk contactForEmail:peerEmail]; if (!user || user.visibility != MEGAUserVisibilityVisible) { - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email") detail:nil image:[UIImage imageNamed:@"add"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email") detail:nil image:[UIImage imageNamed:@"add"] style:UIAlertActionStyleDefault actionHandler:^{ if (MEGAReachabilityManager.isReachableHUDIfNot) { MEGAInviteContactRequestDelegate *inviteContactRequestDelegate = [MEGAInviteContactRequestDelegate.alloc initWithNumberOfRequests:1]; [MEGASdkManager.sharedMEGASdk inviteContactWithEmail:peerEmail message:@"" action:MEGAInviteActionAdd delegate:inviteContactRequestDelegate]; @@ -404,7 +406,7 @@ - (IBAction)didTapPermissionsButton:(UIButton *)sender { } } - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"removeParticipant", @"A button title which removes a participant from a chat.") detail:nil image:[UIImage imageNamed:@"delete"] style:UIAlertActionStyleDestructive actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"removeParticipant", @"A button title which removes a participant from a chat.") detail:nil image:[UIImage imageNamed:@"delete"] style:UIAlertActionStyleDestructive actionHandler:^{ [MEGASdkManager.sharedMEGAChatSdk removeFromChat:self.chatRoom.chatId userHandle:userHandle delegate:weakSelf]; }]]; } else { @@ -412,7 +414,7 @@ - (IBAction)didTapPermissionsButton:(UIButton *)sender { } if (actions.count > 0) { - ActionSheetViewController *permissionsActionSheet = [ActionSheetViewController.alloc initWithActions:actions headerTitle:NSLocalizedString(@"permissions", @"Title of the view that shows the kind of permissions (Read Only, Read & Write or Full Access) that you can give to a shared folder ") dismissCompletion:nil sender:sender]; + ActionSheetViewController *permissionsActionSheet = [ActionSheetViewController.alloc initWithActions:actions headerTitle:LocalizedString(@"permissions", @"Title of the view that shows the kind of permissions (Read Only, Read & Write or Full Access) that you can give to a shared folder ") dismissCompletion:nil sender:sender]; [self presentViewController:permissionsActionSheet animated:YES completion:nil]; } } @@ -514,46 +516,46 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.leftImageView.image = [UIImage imageNamed:@"rename"]; cell.leftImageView.tintColor = [UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]; cell.nameLabel.text = self.chatRoom.isMeeting - ? NSLocalizedString(@"meetings.info.renameMeeting", @"The title of a menu button which allows users to rename a meeting.") - : NSLocalizedString(@"renameGroup", @"The title of a menu button which allows users to rename a group chat."); + ? LocalizedString(@"meetings.info.renameMeeting", @"The title of a menu button which allows users to rename a meeting.") + : LocalizedString(@"renameGroup", @"The title of a menu button which allows users to rename a group chat."); break; case GroupChatDetailsSectionSharedFiles: cell.leftImageView.image = [UIImage imageNamed:@"sharedFiles"]; - cell.nameLabel.text = NSLocalizedString(@"Shared Files", @"Header of block with all shared files in chat."); + cell.nameLabel.text = LocalizedString(@"Shared Files", @"Header of block with all shared files in chat."); cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; break; case GroupChatDetailsSectionGetChatLink: cell.leftImageView.image = [UIImage imageNamed:@"link"]; cell.nameLabel.text = self.chatRoom.isMeeting - ? NSLocalizedString(@"meetings.info.shareMeetingLink", @"Label in a cell where you can share the meeting link") - : NSLocalizedString(@"Get Chat Link", @""); + ? LocalizedString(@"meetings.info.shareMeetingLink", @"Label in a cell where you can share the meeting link") + : LocalizedString(@"Get Chat Link", @""); cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; break; case GroupChatDetailsSectionManageChatHistory: cell.leftImageView.image = [UIImage imageNamed:@"clearChatHistory"]; cell.nameLabel.text = self.chatRoom.isMeeting - ? NSLocalizedString(@"meetings.info.manageMeetingHistory", @"Text related with the section where you can manage the meeting history. There you can for example, clear the history or configure the retention setting.") - : NSLocalizedString(@"Manage Chat History", @"Text related with the section where you can manage the chat history. There you can for example, clear the history or configure the retention setting."); + ? LocalizedString(@"meetings.info.manageMeetingHistory", @"Text related with the section where you can manage the meeting history. There you can for example, clear the history or configure the retention setting.") + : LocalizedString(@"Manage Chat History", @"Text related with the section where you can manage the chat history. There you can for example, clear the history or configure the retention setting."); cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; break; case GroupChatDetailsSectionArchiveChat: cell.leftImageView.image = self.chatRoom.isArchived ? [UIImage imageNamed:@"unArchiveChat"] : [UIImage imageNamed:@"archiveChat"]; cell.leftImageView.tintColor = self.chatRoom.isArchived ? [UIColor mnz_redForTraitCollection:(self.traitCollection)] : [UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]; - cell.nameLabel.text = self.chatRoom.isArchived ? NSLocalizedString(@"unarchiveChat", @"The title of the dialog to unarchive an archived chat.") : NSLocalizedString(@"archiveChat", @"Title of button to archive chats."); + cell.nameLabel.text = self.chatRoom.isArchived ? LocalizedString(@"unarchiveChat", @"The title of the dialog to unarchive an archived chat.") : LocalizedString(@"archiveChat", @"Title of button to archive chats."); [cell setDestructive:self.chatRoom.isArchived]; break; case GroupChatDetailsSectionLeaveGroup: { cell.leftImageView.image = [UIImage imageNamed:@"leaveGroup"]; NSString *leaveTitle = self.chatRoom.isMeeting - ? NSLocalizedString(@"meetings.info.leaveMeeting", @"The button title that allows the user to leave a ad hoc meeting.") - : NSLocalizedString(@"leaveGroup", @"Button title that allows the user to leave a group chat."); + ? LocalizedString(@"meetings.info.leaveMeeting", @"The button title that allows the user to leave a ad hoc meeting.") + : LocalizedString(@"leaveGroup", @"Button title that allows the user to leave a group chat."); cell.nameLabel.text = self.chatRoom.isPreview - ? NSLocalizedString(@"close", nil) + ? LocalizedString(@"close", @"") : leaveTitle; [cell setDestructive:YES]; break; @@ -563,13 +565,13 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.leftImageView.image = [UIImage imageNamed:@"endCall"]; cell.leftImageView.tintColor = [UIColor redColor]; cell.leftImageView.contentMode = UIViewContentModeScaleAspectFit; - cell.nameLabel.text = NSLocalizedString(@"meetings.endCall.endForAllButtonTitle", @"Button title that ends the call for all the participants."); + cell.nameLabel.text = LocalizedString(@"meetings.endCall.endForAllButtonTitle", @"Button title that ends the call for all the participants."); [cell setDestructive:YES]; cell.accessoryType = UITableViewCellAccessoryNone; break; case GroupChatDetailsSectionEncryptedKeyRotation: - cell.nameLabel.text = self.chatRoom.isPublicChat ? NSLocalizedString(@"Enable Encrypted Key Rotation", @"Title show in a cell where the users can enable the 'Encrypted Key Rotation'") : NSLocalizedString(@"Encrypted Key Rotation", @"Label in a cell where you can enable the 'Encrypted Key Rotation'"); + cell.nameLabel.text = self.chatRoom.isPublicChat ? LocalizedString(@"Enable Encrypted Key Rotation", @"Title show in a cell where the users can enable the 'Encrypted Key Rotation'") : LocalizedString(@"Encrypted Key Rotation", @"Label in a cell where you can enable the 'Encrypted Key Rotation'"); cell.leftImageView.hidden = YES; if (self.chatRoom.isPublicChat) { cell.enableLabel.hidden = YES; @@ -578,13 +580,13 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.enableLabel.hidden = cell.userInteractionEnabled = NO; } cell.accessoryType = cell.enableLabel.hidden ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone; - cell.enableLabel.text = cell.enableLabel.hidden ? @"" : NSLocalizedString(@"Enabled", @"The label of the toggle switch to indicate that file versioning is enabled."); + cell.enableLabel.text = cell.enableLabel.hidden ? @"" : LocalizedString(@"Enabled", @"The label of the toggle switch to indicate that file versioning is enabled."); break; case GroupChatDetailsSectionObservers: cell = [self.tableView dequeueReusableCellWithIdentifier:@"GroupChatDetailsObserversTypeID" forIndexPath:indexPath]; cell.leftImageView.image = [UIImage imageNamed:@"chatObservers"]; - cell.emailLabel.text = NSLocalizedString(@"Observers", @"Users previewing a public chat"); + cell.emailLabel.text = LocalizedString(@"Observers", @"Users previewing a public chat"); cell.rightLabel.text = [NSString stringWithFormat:@"%tu", self.chatRoom.previewersCount]; break; @@ -592,7 +594,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N if ((indexPath.row == 0) && [self shouldShowAddParticipants]) { cell = [self.tableView dequeueReusableCellWithIdentifier:@"GroupChatDetailsParticipantEmailTypeID" forIndexPath:indexPath]; cell.leftImageView.image = [UIImage imageNamed:@"inviteToChat"]; - cell.emailLabel.text = NSLocalizedString(@"addParticipant", @"Button label. Allows to add contacts in current chat conversation."); + cell.emailLabel.text = LocalizedString(@"addParticipant", @"Button label. Allows to add contacts in current chat conversation."); cell.onlineStatusView.backgroundColor = nil; [cell.permissionsButton setImage:nil forState:UIControlStateNormal]; cell.permissionsButton.tag = indexPath.row; @@ -614,7 +616,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N if (handle == [[MEGASdkManager sharedMEGAChatSdk] myUserHandle]) { NSString *myFullname = [[MEGASdkManager sharedMEGAChatSdk] myFullname]; - peerDisplayName = [NSString stringWithFormat:@"%@ (%@)", myFullname, NSLocalizedString(@"me", @"The title for my message in a chat. The message was sent from yourself.")]; + peerDisplayName = [NSString stringWithFormat:@"%@ (%@)", myFullname, LocalizedString(@"me", @"The title for my message in a chat. The message was sent from yourself.")]; peerEmail = [[MEGASdkManager sharedMEGAChatSdk] myEmail]; privilege = self.chatRoom.ownPrivilege; } else { @@ -757,7 +759,7 @@ - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger case GroupChatDetailsSectionParticipants: headerView.titleLabel.font = [UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium]; - [headerView configureWithTitle:NSLocalizedString(@"participants", @"Label to describe the section where you can see the participants of a group chat") topDistance:4.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; + [headerView configureWithTitle:LocalizedString(@"participants", @"Label to describe the section where you can see the participants of a group chat") topDistance:4.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; break; case GroupChatDetailsSectionAllowNonHostToAddParticipants: @@ -818,9 +820,9 @@ - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger footerView.titleLabel.numberOfLines = 0; footerView.titleLabel.lineBreakMode = NSLineBreakByWordWrapping; if (self.chatRoom.peerCount < 100) { - [footerView configureWithTitle:[NSLocalizedString(@"Key rotation is slightly more secure, but does not allow you to create a chat link and new participants will not see past messages.", @"Footer text to explain what means 'Encrypted Key Rotation'") stringByAppendingString:@"\n"] topDistance:self.chatRoom.isPublicChat ? (self.chatRoom.ownPrivilege >= MEGAChatRoomPrivilegeModerator) ? 10.0f : 1.0f : 10.0f isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; + [footerView configureWithTitle:[LocalizedString(@"Key rotation is slightly more secure, but does not allow you to create a chat link and new participants will not see past messages.", @"Footer text to explain what means 'Encrypted Key Rotation'") stringByAppendingString:@"\n"] topDistance:self.chatRoom.isPublicChat ? (self.chatRoom.ownPrivilege >= MEGAChatRoomPrivilegeModerator) ? 10.0f : 1.0f : 10.0f isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; } else { - [footerView configureWithTitle:NSLocalizedString(@"Key rotation is disabled for conversations with more than 100 participants.", @"Footer to explain why key rotation is disabled for public chats with many participants") topDistance:self.chatRoom.isPublicChat ? (self.chatRoom.ownPrivilege >= MEGAChatRoomPrivilegeModerator) ? 10.f : 1.0f : 10.0f isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; + [footerView configureWithTitle:LocalizedString(@"Key rotation is disabled for conversations with more than 100 participants.", @"Footer to explain why key rotation is disabled for public chats with many participants") topDistance:self.chatRoom.isPublicChat ? (self.chatRoom.ownPrivilege >= MEGAChatRoomPrivilegeModerator) ? 10.f : 1.0f : 10.0f isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; } } else { [footerView configureWithTitle:nil topDistance:1.0f isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; @@ -876,10 +878,10 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath }]; [[MEGASdkManager sharedMEGAChatSdk] queryChatLink:self.chatRoom.chatId delegate:delegate]; } else { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Chat Link", @"Label shown in a cell where you can enable a switch to get a chat link") message:NSLocalizedString(@"To create a chat link you must name the group.", @"Alert message to advice the users that to generate a chat link they need enter a group name for the chat") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"Chat Link", @"Label shown in a cell where you can enable a switch to get a chat link") message:LocalizedString(@"To create a chat link you must name the group.", @"Alert message to advice the users that to generate a chat link they need enter a group name for the chat") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self renameChatGroup]; }]]; [self presentViewController:alertController animated:YES completion:nil]; @@ -929,10 +931,10 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath case GroupChatDetailsSectionEncryptedKeyRotation: { CustomModalAlertViewController *customModalAlertVC = [[CustomModalAlertViewController alloc] init]; customModalAlertVC.image = [UIImage imageNamed:@"lock"]; - customModalAlertVC.viewTitle = NSLocalizedString(@"Enable Encrypted Key Rotation", @"Title show in a cell where the users can enable the 'Encrypted Key Rotation'"); - customModalAlertVC.detail = NSLocalizedString(@"Key rotation is slightly more secure, but does not allow you to create a chat link and new participants will not see past messages.", @"Footer text to explain what means 'Encrypted Key Rotation'"); - customModalAlertVC.firstButtonTitle = NSLocalizedString(@"enable", nil); - customModalAlertVC.dismissButtonTitle = NSLocalizedString(@"cancel", nil); + customModalAlertVC.viewTitle = LocalizedString(@"Enable Encrypted Key Rotation", @"Title show in a cell where the users can enable the 'Encrypted Key Rotation'"); + customModalAlertVC.detail = LocalizedString(@"Key rotation is slightly more secure, but does not allow you to create a chat link and new participants will not see past messages.", @"Footer text to explain what means 'Encrypted Key Rotation'"); + customModalAlertVC.firstButtonTitle = LocalizedString(@"enable", @""); + customModalAlertVC.dismissButtonTitle = LocalizedString(@"cancel", @""); __weak typeof(CustomModalAlertViewController) *weakCustom = customModalAlertVC; customModalAlertVC.firstCompletion = ^{ MEGAChatGenericRequestDelegate *delegate = [[MEGAChatGenericRequestDelegate alloc] initWithCompletion:^(MEGAChatRequest * _Nonnull request, MEGAChatError * _Nonnull error) { @@ -1023,8 +1025,8 @@ - (void)onChatRequestFinish:(MEGAChatSdk *)api request:(MEGAChatRequest *)reques case MEGAChatRequestTypeTruncateHistory: { NSString *message = self.chatRoom.isMeeting - ? NSLocalizedString(@"meetings.info.manageMeetingHistory.meetingHistoryHasBeenCleared", @"Message show when the history of a meeting has been successfully deleted") - : NSLocalizedString(@"Chat History has Been Cleared", @"Message show when the history of a chat has been successfully deleted"); + ? LocalizedString(@"meetings.info.manageMeetingHistory.meetingHistoryHasBeenCleared", @"Message show when the history of a meeting has been successfully deleted") + : LocalizedString(@"Chat History has Been Cleared", @"Message show when the history of a chat has been successfully deleted"); [SVProgressHUD showImage:[UIImage imageNamed:@"clearChatHistory"] status:message]; break; } diff --git a/iMEGA/Chat/LocationSearchTableViewCell.h b/iMEGA/Chat/LocationSearchTableViewCell.h index 4fd73d474f..00caac5025 100644 --- a/iMEGA/Chat/LocationSearchTableViewCell.h +++ b/iMEGA/Chat/LocationSearchTableViewCell.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Chat/LocationSearchTableViewCell.m b/iMEGA/Chat/LocationSearchTableViewCell.m index 3858565fc2..eade685c7e 100644 --- a/iMEGA/Chat/LocationSearchTableViewCell.m +++ b/iMEGA/Chat/LocationSearchTableViewCell.m @@ -1,4 +1,3 @@ - #import "LocationSearchTableViewCell.h" @implementation LocationSearchTableViewCell diff --git a/iMEGA/Chat/LocationSearchTableViewController.h b/iMEGA/Chat/LocationSearchTableViewController.h index e8f4564589..74e3f4e3e6 100644 --- a/iMEGA/Chat/LocationSearchTableViewController.h +++ b/iMEGA/Chat/LocationSearchTableViewController.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Chat/LocationSearchTableViewController.m b/iMEGA/Chat/LocationSearchTableViewController.m index bdf6ae419c..b8aa285cd6 100644 --- a/iMEGA/Chat/LocationSearchTableViewController.m +++ b/iMEGA/Chat/LocationSearchTableViewController.m @@ -1,10 +1,11 @@ - #import "LocationSearchTableViewController.h" #import #import "LocationSearchTableViewCell.h" +@import MEGAL10nObjc; + @interface LocationSearchTableViewController () @property (nonatomic) NSArray *mapItems; @@ -16,7 +17,7 @@ @implementation LocationSearchTableViewController - (void)viewDidLoad { [super viewDidLoad]; - self.navigationItem.title = NSLocalizedString(@"Send Location", @"Alert title shown when the user opens a shared Geolocation for the first time from any client, we will show a confirmation dialog warning the user that he is now leaving the E2EE paradigm"); + self.navigationItem.title = LocalizedString(@"Send Location", @"Alert title shown when the user opens a shared Geolocation for the first time from any client, we will show a confirmation dialog warning the user that he is now leaving the E2EE paradigm"); } - (void)viewDidAppear:(BOOL)animated { diff --git a/iMEGA/Chat/MEGACallManager.h b/iMEGA/Chat/MEGACallManager.h index b0039b5151..783e16f463 100644 --- a/iMEGA/Chat/MEGACallManager.h +++ b/iMEGA/Chat/MEGACallManager.h @@ -1,4 +1,3 @@ - #import @class MEGAChatCall; diff --git a/iMEGA/Chat/MEGACallManager.m b/iMEGA/Chat/MEGACallManager.m index 61ea61639c..44e379e327 100644 --- a/iMEGA/Chat/MEGACallManager.m +++ b/iMEGA/Chat/MEGACallManager.m @@ -1,4 +1,3 @@ - #import "MEGACallManager.h" #import "MEGASdkManager.h" diff --git a/iMEGA/Chat/MEGAProviderDelegate.h b/iMEGA/Chat/MEGAProviderDelegate.h index 7df1e77b4a..2f6789b041 100644 --- a/iMEGA/Chat/MEGAProviderDelegate.h +++ b/iMEGA/Chat/MEGAProviderDelegate.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Chat/MEGAProviderDelegate.m b/iMEGA/Chat/MEGAProviderDelegate.m index b6c52a805c..d59e8799ae 100644 --- a/iMEGA/Chat/MEGAProviderDelegate.m +++ b/iMEGA/Chat/MEGAProviderDelegate.m @@ -1,4 +1,3 @@ - #import "MEGAProviderDelegate.h" #import @@ -13,6 +12,8 @@ #import "MEGANavigationController.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @interface MEGAProviderDelegate () @property (nonatomic, strong) MEGACallManager *megaCallManager; @@ -108,7 +109,7 @@ - (void)reportIncomingCallWithCallId:(uint64_t)callId chatId:(uint64_t)chatId co completion:completion]; } else { [self reportNewIncomingCallWithValue:[MEGASdk base64HandleForUserHandle:chatId] - callerName:NSLocalizedString(@"connecting", nil) + callerName:LocalizedString(@"connecting", @"") hasVideo:NO uuid:uuid callId:callId diff --git a/iMEGA/Chat/MEGAProviderDelegate.swift b/iMEGA/Chat/MEGAProviderDelegate.swift index e08dcf9ec8..05f8da0224 100644 --- a/iMEGA/Chat/MEGAProviderDelegate.swift +++ b/iMEGA/Chat/MEGAProviderDelegate.swift @@ -1,4 +1,3 @@ - import Foundation extension MEGAProviderDelegate { diff --git a/iMEGA/Chat/ManageHistoryScene/HistoryRetentionPickerViewModel.swift b/iMEGA/Chat/ManageHistoryScene/HistoryRetentionPickerViewModel.swift index a352d768d4..a32db7756f 100644 --- a/iMEGA/Chat/ManageHistoryScene/HistoryRetentionPickerViewModel.swift +++ b/iMEGA/Chat/ManageHistoryScene/HistoryRetentionPickerViewModel.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n import MEGAPresentation struct UnitsComponentValues { diff --git a/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryTableViewController.swift b/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryTableViewController.swift index 1abed4e6c0..7a21a21a72 100644 --- a/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryTableViewController.swift +++ b/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryTableViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGAPresentation import UIKit diff --git a/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryViewModel.swift b/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryViewModel.swift index 32894ff72d..79f0e27e49 100644 --- a/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryViewModel.swift +++ b/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryViewModel.swift @@ -1,6 +1,6 @@ - import Foundation import MEGADomain +import MEGAL10n import MEGAPresentation enum ManageHistoryAction: ActionType { diff --git a/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryViewRouter.swift b/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryViewRouter.swift index 651ccf7e32..09ce6cce85 100644 --- a/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryViewRouter.swift +++ b/iMEGA/Chat/ManageHistoryScene/ManageChatHistoryViewRouter.swift @@ -1,4 +1,4 @@ - +import ChatRepo import Foundation import MEGADomain @@ -27,7 +27,7 @@ final class ManageChatHistoryViewRouter: NSObject, ManageChatHistoryProtocol { } func build() -> UIViewController { - let repository = ManageChatHistoryRepository(chatSdk: MEGASdkManager.sharedMEGAChatSdk()) + let repository = ManageChatHistoryRepository(chatSdk: .shared) let manageChatHistoryUseCase = ManageChatHistoryUseCase(retentionValueUseCase: HistoryRetentionUseCase(repository: repository), historyRetentionUseCase: HistoryRetentionUseCase(repository: repository), clearChatHistoryUseCase: ClearChatHistoryUseCase(repository: repository)) let viewModel = ManageChatHistoryViewModel(router: self, manageChatHistoryUseCase: manageChatHistoryUseCase, diff --git a/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.h b/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.h index a43d598c8e..772bf693cb 100644 --- a/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.h +++ b/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.h @@ -1,4 +1,3 @@ - #import "MEGAChatMessage.h" @class MEGAChatRoom; diff --git a/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.m b/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.m index 04ab5b0b1f..2004707548 100644 --- a/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.m +++ b/iMEGA/Chat/Model/MEGAChatMessage+MNZCategory.m @@ -1,4 +1,3 @@ - #import "MEGAChatMessage+MNZCategory.h" #import @@ -7,7 +6,6 @@ #import "MEGAChatGenericRequestDelegate.h" #import "MEGAFetchNodesRequestDelegate.h" -#import "MEGAGenericRequestDelegate.h" #import "MEGASdkManager.h" #import "MEGAStore.h" #import "NSAttributedString+MNZCategory.h" @@ -15,6 +13,8 @@ #import "NSURL+MNZCategory.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + static const void *chatIdTagKey = &chatIdTagKey; static const void *attributedTextTagKey = &attributedTextTagKey; static const void *warningDialogTagKey = &warningDialogTagKey; @@ -125,7 +125,7 @@ - (NSString *)generateAttributedString { UIFont *textFontMediumFootnote = [[UIFont preferredFontForTextStyle:UIFontTextStyleFootnote] fontWithWeight:UIFontWeightMedium]; if (self.isDeleted) { - text = NSLocalizedString(@"thisMessageHasBeenDeleted", @"A log message in a chat to indicate that the message has been deleted by the user."); + text = LocalizedString(@"thisMessageHasBeenDeleted", @"A log message in a chat to indicate that the message has been deleted by the user."); } else if (self.isManagementMessage) { NSString *fullNameDidAction = [self fullNameDidAction]; NSString *fullNameReceiveAction = [self fullNameReceiveAction]; @@ -135,7 +135,7 @@ - (NSString *)generateAttributedString { switch (self.privilege) { case -1: { if (fullNameDidAction && ![fullNameReceiveAction isEqualToString:fullNameDidAction]) { - NSString *wasRemovedFromTheGroupChatBy = NSLocalizedString(@"wasRemovedFromTheGroupChatBy", @"A log message in a chat conversation to tell the reader that a participant [A] was removed from the group chat by the moderator [B]. Please keep [A] and [B], they will be replaced by the participant and the moderator names at runtime. For example: Alice was removed from the group chat by Frank."); + NSString *wasRemovedFromTheGroupChatBy = LocalizedString(@"wasRemovedFromTheGroupChatBy", @"A log message in a chat conversation to tell the reader that a participant [A] was removed from the group chat by the moderator [B]. Please keep [A] and [B], they will be replaced by the participant and the moderator names at runtime. For example: Alice was removed from the group chat by Frank."); wasRemovedFromTheGroupChatBy = [wasRemovedFromTheGroupChatBy stringByReplacingOccurrencesOfString:@"[A]" withString:fullNameReceiveAction]; wasRemovedFromTheGroupChatBy = [wasRemovedFromTheGroupChatBy stringByReplacingOccurrencesOfString:@"[B]" withString:fullNameDidAction]; text = wasRemovedFromTheGroupChatBy; @@ -145,7 +145,7 @@ - (NSString *)generateAttributedString { [mutableAttributedString addAttributes:@{ NSFontAttributeName: textFontMedium, NSFontAttributeName: [self chatPeerOptionsUrlStringForUserHandle:self.userHandle] } range:[wasRemovedFromTheGroupChatBy rangeOfString:fullNameDidAction]]; self.attributedText = mutableAttributedString; } else { - NSString *leftTheGroupChat = NSLocalizedString(@"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."); + NSString *leftTheGroupChat = LocalizedString(@"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; @@ -158,7 +158,7 @@ - (NSString *)generateAttributedString { case -2: { if (fullNameDidAction && ![fullNameReceiveAction isEqualToString:fullNameDidAction]) { - NSString *joinedTheGroupChatByInvitationFrom = NSLocalizedString(@"joinedTheGroupChatByInvitationFrom", @"A log message in a chat conversation to tell the reader that a participant [A] was added to the chat by a moderator [B]. Please keep the [A] and [B] placeholders, they will be replaced by the participant and the moderator names at runtime. For example: Alice joined the group chat by invitation from Frank."); + NSString *joinedTheGroupChatByInvitationFrom = LocalizedString(@"joinedTheGroupChatByInvitationFrom", @"A log message in a chat conversation to tell the reader that a participant [A] was added to the chat by a moderator [B]. Please keep the [A] and [B] placeholders, they will be replaced by the participant and the moderator names at runtime. For example: Alice joined the group chat by invitation from Frank."); joinedTheGroupChatByInvitationFrom = [joinedTheGroupChatByInvitationFrom stringByReplacingOccurrencesOfString:@"[A]" withString:fullNameReceiveAction]; joinedTheGroupChatByInvitationFrom = [joinedTheGroupChatByInvitationFrom stringByReplacingOccurrencesOfString:@"[B]" withString:fullNameDidAction]; text = joinedTheGroupChatByInvitationFrom; @@ -168,7 +168,7 @@ - (NSString *)generateAttributedString { [mutableAttributedString addAttributes:@{ NSFontAttributeName: textFontMedium, NSFontAttributeName: [self chatPeerOptionsUrlStringForUserHandle:self.userHandle] } range:[joinedTheGroupChatByInvitationFrom rangeOfString:fullNameDidAction]]; self.attributedText = mutableAttributedString; } else { - NSString *joinedTheGroupChat = [NSString stringWithFormat:NSLocalizedString(@"%@ joined the group chat.", @"Management message shown in a chat when the user %@ joined it from a public chat link"), fullNameReceiveAction]; + NSString *joinedTheGroupChat = [NSString stringWithFormat:LocalizedString(@"%@ joined the group chat.", @"Management message shown in a chat when the user %@ joined it from a public chat link"), fullNameReceiveAction]; text = joinedTheGroupChat; NSMutableAttributedString *mutableAttributedString = [NSMutableAttributedString.alloc initWithString:joinedTheGroupChat attributes:@{NSFontAttributeName:textFontRegular, NSForegroundColorAttributeName:UIColor.mnz_label}]; @@ -185,7 +185,7 @@ - (NSString *)generateAttributedString { break; case MEGAChatMessageTypeTruncate: { - NSString *clearedTheChatHistory = NSLocalizedString(@"clearedTheChatHistory", @"A log message in the chat conversation to tell the reader that a participant [A] cleared the history of the chat. For example, Alice cleared the chat history."); + NSString *clearedTheChatHistory = LocalizedString(@"clearedTheChatHistory", @"A log message in the chat conversation to tell the reader that a participant [A] cleared the history of the chat. For example, Alice cleared the chat history."); clearedTheChatHistory = [clearedTheChatHistory stringByReplacingOccurrencesOfString:@"[A]" withString:fullNameDidAction]; text = clearedTheChatHistory; @@ -199,15 +199,15 @@ - (NSString *)generateAttributedString { NSString *wasChangedToBy; switch (self.privilege) { case 0: - wasChangedToBy = NSLocalizedString(@"chat.message.changedRole.readOnly", @"A log message in a chat to display that a participant's permission was changed to read-only and by whom"); + wasChangedToBy = LocalizedString(@"chat.message.changedRole.readOnly", @"A log message in a chat to display that a participant's permission was changed to read-only and by whom"); break; case 2: - wasChangedToBy = NSLocalizedString(@"chat.message.changedRole.standard", @"A log message in a chat to display that a participant's permission was changed to standard role and by whom"); + wasChangedToBy = LocalizedString(@"chat.message.changedRole.standard", @"A log message in a chat to display that a participant's permission was changed to standard role and by whom"); break; case 3: - wasChangedToBy = NSLocalizedString(@"chat.message.changedRole.host", @"A log message in a chat to display that a participant's permission was changed to host role and by whom"); + wasChangedToBy = LocalizedString(@"chat.message.changedRole.host", @"A log message in a chat to display that a participant's permission was changed to host role and by whom"); break; default: @@ -229,7 +229,7 @@ - (NSString *)generateAttributedString { case MEGAChatMessageTypeSetRetentionTime: { if (self.retentionTime <= 0) { - text = NSLocalizedString(@"[A]%1$s[/A][B] disabled message clearing.[/B]", @"System message that is shown to all chat participants upon disabling the Retention history."); + text = LocalizedString(@"[A]%1$s[/A][B] disabled message clearing.[/B]", @"System message that is shown to all chat participants upon disabling the Retention history."); text = [text stringByReplacingOccurrencesOfString:@"%1$s" withString:fullNameDidAction]; text = text.mnz_removeWebclientFormatters; @@ -239,7 +239,7 @@ - (NSString *)generateAttributedString { self.attributedText = mutableAttributedString; } else { - text = NSLocalizedString(@"[A]%1$s[/A][B] changed the message clearing time to[/B][A] %2$s[/A][B].[/B]", @"System message displayed to all chat participants when one of them enables retention history"); + text = LocalizedString(@"[A]%1$s[/A][B] changed the message clearing time to[/B][A] %2$s[/A][B].[/B]", @"System message displayed to all chat participants when one of them enables retention history"); text = [text stringByReplacingOccurrencesOfString:@"%1$s" withString:fullNameDidAction]; @@ -257,7 +257,7 @@ - (NSString *)generateAttributedString { } case MEGAChatMessageTypeChatTitle: { - NSString *changedGroupChatNameTo = NSLocalizedString(@"changedGroupChatNameTo", @"A hint message in a group chat to indicate the group chat name is changed to a new one. Please keep %s when translating this string which will be replaced with the name at runtime."); + NSString *changedGroupChatNameTo = LocalizedString(@"changedGroupChatNameTo", @"A hint message in a group chat to indicate the group chat name is changed to a new one. Please keep %s when translating this string which will be replaced with the name at runtime."); changedGroupChatNameTo = [changedGroupChatNameTo stringByReplacingOccurrencesOfString:@"[A]" withString:fullNameDidAction]; changedGroupChatNameTo = [changedGroupChatNameTo stringByReplacingOccurrencesOfString:@"[B]" withString:(self.content ? self.content : @" ")]; text = changedGroupChatNameTo; @@ -270,7 +270,7 @@ - (NSString *)generateAttributedString { } case MEGAChatMessageTypePublicHandleCreate: { - NSString *publicHandleCreated = [NSString stringWithFormat:NSLocalizedString(@"%@ created a public link for the chat.", @"Management message shown in a chat when the user %@ creates a public link for the chat"), fullNameReceiveAction]; + NSString *publicHandleCreated = [NSString stringWithFormat:LocalizedString(@"%@ created a public link for the chat.", @"Management message shown in a chat when the user %@ creates a public link for the chat"), fullNameReceiveAction]; text = publicHandleCreated; NSMutableAttributedString *mutableAttributedString = [NSMutableAttributedString.alloc initWithString:publicHandleCreated attributes:@{NSFontAttributeName:textFontRegular, NSForegroundColorAttributeName:UIColor.mnz_label}]; @@ -281,7 +281,7 @@ - (NSString *)generateAttributedString { } case MEGAChatMessageTypePublicHandleDelete: { - NSString *publicHandleRemoved = [NSString stringWithFormat:NSLocalizedString(@"%@ removed a public link for the chat.", @"Management message shown in a chat when the user %@ removes a public link for the chat"), fullNameReceiveAction]; + NSString *publicHandleRemoved = [NSString stringWithFormat:LocalizedString(@"%@ removed a public link for the chat.", @"Management message shown in a chat when the user %@ removes a public link for the chat"), fullNameReceiveAction]; text = publicHandleRemoved; NSMutableAttributedString *mutableAttributedString = [NSMutableAttributedString.alloc initWithString:publicHandleRemoved attributes:@{NSFontAttributeName:textFontRegular, NSForegroundColorAttributeName:UIColor.mnz_label}]; @@ -292,13 +292,13 @@ - (NSString *)generateAttributedString { } case MEGAChatMessageTypeSetPrivateMode: { - NSString *setPrivateMode = [NSString stringWithFormat:NSLocalizedString(@"%@ enabled Encrypted Key Rotation", @"Management message shown in a chat when the user %@ enables the 'Encrypted Key Rotation'"), fullNameReceiveAction]; - NSString *keyRotationExplanation = NSLocalizedString(@"Key rotation is slightly more secure, but does not allow you to create a chat link and new participants will not see past messages.", @"Footer text to explain what means 'Encrypted Key Rotation'"); + NSString *setPrivateMode = [NSString stringWithFormat:LocalizedString(@"%@ enabled Encrypted Key Rotation", @"Management message shown in a chat when the user %@ enables the 'Encrypted Key Rotation'"), fullNameReceiveAction]; + NSString *keyRotationExplanation = LocalizedString(@"Key rotation is slightly more secure, but does not allow you to create a chat link and new participants will not see past messages.", @"Footer text to explain what means 'Encrypted Key Rotation'"); text = [NSString stringWithFormat:@"%@\n\n%@", setPrivateMode, keyRotationExplanation]; NSMutableAttributedString *mutableAttributedString = [NSMutableAttributedString.alloc initWithString:text attributes:@{NSFontAttributeName:textFontRegular, NSForegroundColorAttributeName:UIColor.mnz_label}]; [mutableAttributedString addAttributes:@{ NSFontAttributeName: textFontMedium, NSFontAttributeName: [self chatPeerOptionsUrlStringForUserHandle:[self userHandleReceiveAction]] } range:[text rangeOfString:fullNameReceiveAction]]; - [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[text rangeOfString:NSLocalizedString(@"Encrypted Key Rotation", nil)]]; + [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMedium range:[text rangeOfString:LocalizedString(@"Encrypted Key Rotation", @"")]]; [mutableAttributedString addAttribute:NSFontAttributeName value:textFontMediumFootnote range:[text rangeOfString:keyRotationExplanation]]; [mutableAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor mnz_secondaryGrayForTraitCollection:UIScreen.mainScreen.traitCollection] range:[text rangeOfString:keyRotationExplanation]]; @@ -335,7 +335,7 @@ - (NSString *)generateAttributedString { color:textColor]; if (self.isEdited && self.type != MEGAChatMessageTypeContainsMeta) { - NSAttributedString *edited = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" %@", NSLocalizedString(@"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}]; + NSAttributedString *edited = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" %@", LocalizedString(@"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; diff --git a/iMEGA/Chat/SendLinkToChatsDelegate.swift b/iMEGA/Chat/SendLinkToChatsDelegate.swift index 1bff3cec0c..425e442424 100644 --- a/iMEGA/Chat/SendLinkToChatsDelegate.swift +++ b/iMEGA/Chat/SendLinkToChatsDelegate.swift @@ -1,3 +1,4 @@ +import MEGAL10n class SendLinkToChatsDelegate: NSObject { @@ -20,20 +21,20 @@ extension SendLinkToChatsDelegate: SendToViewControllerDelegate { } chats.forEach { - MEGASdkManager.sharedMEGAChatSdk().sendMessage(toChat: $0.chatId, message: link) + MEGAChatSdk.shared.sendMessage(toChat: $0.chatId, message: link) } users.forEach { - let chatRoom = MEGASdkManager.sharedMEGAChatSdk().chatRoom(byUser: $0.handle) + let chatRoom = MEGAChatSdk.shared.chatRoom(byUser: $0.handle) if chatRoom != nil { guard let chatId = chatRoom?.chatId else { return } - MEGASdkManager.sharedMEGAChatSdk().sendMessage(toChat: chatId, message: link) + MEGAChatSdk.shared.sendMessage(toChat: chatId, message: link) } else { MEGALogDebug("There is not a chat with %@, create the chat and send message", $0.email) - MEGASdkManager.sharedMEGAChatSdk().mnz_createChatRoom(userHandle: $0.handle, completion: { - MEGASdkManager.sharedMEGAChatSdk().sendMessage(toChat: $0.chatId, message: self.link) + MEGAChatSdk.shared.mnz_createChatRoom(userHandle: $0.handle, completion: { + MEGAChatSdk.shared.sendMessage(toChat: $0.chatId, message: self.link) }) } } diff --git a/iMEGA/Chat/SendToViewController.h b/iMEGA/Chat/SendToViewController.h index 0baf0aa912..6b653538c6 100644 --- a/iMEGA/Chat/SendToViewController.h +++ b/iMEGA/Chat/SendToViewController.h @@ -1,4 +1,3 @@ - #import #import "MEGAChatListItem.h" diff --git a/iMEGA/Chat/SendToViewController.m b/iMEGA/Chat/SendToViewController.m index d5be1de3b5..78f70279a1 100644 --- a/iMEGA/Chat/SendToViewController.m +++ b/iMEGA/Chat/SendToViewController.m @@ -27,6 +27,7 @@ #import "NSString+MNZCategory.h" @import DZNEmptyDataSet; @import MEGAUIKit; +@import MEGAL10nObjc; @interface SendToViewController () @@ -79,7 +80,7 @@ - (void)viewDidLoad { case SendModeForward: case SendModeFileAndFolderLink: case SendModeText: - self.cancelBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.cancelBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); break; case SendModeShareExtension: @@ -87,10 +88,10 @@ - (void)viewDidLoad { break; } - self.sendBarButtonItem.title = NSLocalizedString(@"send", @"Label for any 'Send' button, link, text, title, etc. - (String as short as possible)."); + self.sendBarButtonItem.title = LocalizedString(@"send", @"Label for any 'Send' button, link, text, title, etc. - (String as short as possible)."); [self.sendBarButtonItem setTitleTextAttributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleBody weight:UIFontWeightSemibold]} forState:UIControlStateNormal]; - self.navigationItem.title = NSLocalizedString(@"selectDestination", @"Title shown on the navigation bar to explain that you have to choose a destination for the files and/or folders in case you copy, move, import or do some action with them."); + self.navigationItem.title = LocalizedString(@"selectDestination", @"Title shown on the navigation bar to explain that you have to choose a destination for the files and/or folders in case you copy, move, import or do some action with them."); self.searchController = [UISearchController customSearchControllerWithSearchResultsUpdaterDelegate:self searchBarDelegate:self]; self.searchController.definesPresentationContext = YES; @@ -232,9 +233,9 @@ - (void)updateNavigationBarTitle { NSString *navigationTitle; NSInteger selectedItems = (self.selectedGroupChatsMutableArray.count + self.selectedUsersMutableArray.count); if (selectedItems == 0) { - navigationTitle = NSLocalizedString(@"selectDestination", @"Title shown on the navigation bar to explain that you have to choose a destination for the files and/or folders in case you copy, move, import or do some action with them."); + navigationTitle = LocalizedString(@"selectDestination", @"Title shown on the navigation bar to explain that you have to choose a destination for the files and/or folders in case you copy, move, import or do some action with them."); } else { - navigationTitle = (selectedItems == 1) ? NSLocalizedString(@"1 selected", @"Title shown when multiselection is enabled and only one item has been selected.") : [NSString stringWithFormat:NSLocalizedString(@"xSelected", @"Title shown when multiselection is enabled and the user has more than one item selected."), selectedItems]; + navigationTitle = (selectedItems == 1) ? LocalizedString(@"1 selected", @"Title shown when multiselection is enabled and only one item has been selected.") : [NSString stringWithFormat:LocalizedString(@"xSelected", @"Title shown when multiselection is enabled and the user has more than one item selected."), selectedItems]; } self.navigationItem.title = navigationTitle; @@ -268,18 +269,18 @@ - (void)updateMainSearchArray { - (void)showSuccessMessage { NSString *status; if (self.nodes == nil) { - status = NSLocalizedString(@"Shared successfully", @"Shared successfully"); + status = LocalizedString(@"Shared successfully", @"Shared successfully"); } else if (self.nodes.count == 1) { if ((self.selectedGroupChatsMutableArray.count + self.selectedUsersMutableArray.count) == 1) { - status = NSLocalizedString(@"fileSentToChat", @"Toast text upon sending a single file to chat"); + status = LocalizedString(@"fileSentToChat", @"Toast text upon sending a single file to chat"); } else { - status = [NSString stringWithFormat:NSLocalizedString(@"fileSentToXChats", @"Success message when the attachment has been sent to a many chats"), (self.selectedGroupChatsMutableArray.count + self.selectedUsersMutableArray.count) ]; + status = [NSString stringWithFormat:LocalizedString(@"fileSentToXChats", @"Success message when the attachment has been sent to a many chats"), (self.selectedGroupChatsMutableArray.count + self.selectedUsersMutableArray.count) ]; } } else { if ((self.selectedGroupChatsMutableArray.count + self.selectedUsersMutableArray.count) == 1) { - status = NSLocalizedString(@"filesSentToChat", @"Toast text upon sending multiple files to chat"); + status = LocalizedString(@"filesSentToChat", @"Toast text upon sending multiple files to chat"); } else { - status = [NSString stringWithFormat:NSLocalizedString(@"xfilesSentSuccesfully", @"success message when sending multiple files. Please do not modify the %d placeholder."), self.nodes.count]; + status = [NSString stringWithFormat:LocalizedString(@"xfilesSentSuccesfully", @"success message when sending multiple files. Please do not modify the %d placeholder."), self.nodes.count]; } } @@ -701,13 +702,13 @@ - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger headerView.titleLabel.font = [UIFont mnz_preferredFontWithStyle:UIFontTextStyleFootnote weight:UIFontWeightMedium]; switch (section) { case 0: { - [headerView configureWithTitle:self.searchController.isActive ? NSLocalizedString(@"My chats", @"Column header of my contacts/chats at copy dialog") : NSLocalizedString(@"Recents", @"Title for the recents section") topDistance:17.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; + [headerView configureWithTitle:self.searchController.isActive ? LocalizedString(@"My chats", @"Column header of my contacts/chats at copy dialog") : LocalizedString(@"Recents", @"Title for the recents section") topDistance:17.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; return headerView; } case 1: { - [headerView configureWithTitle:NSLocalizedString(@"My chats", @"Column header of my contacts/chats at copy dialog") topDistance:10.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; + [headerView configureWithTitle:LocalizedString(@"My chats", @"Column header of my contacts/chats at copy dialog") topDistance:10.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; return headerView; } @@ -802,10 +803,10 @@ - (NSString *)titleForEmptyState { NSString *text = @""; if (self.searchController.isActive) { if (self.searchController.searchBar.text.length) { - text = NSLocalizedString(@"noResults", @"Title shown when you make a search and there is 'No Results'"); + text = LocalizedString(@"noResults", @"Title shown when you make a search and there is 'No Results'"); } } else { - text = NSLocalizedString(@"contactsEmptyState_title", @"Title shown when the Contacts section is empty, when you have not added any contact."); + text = LocalizedString(@"contactsEmptyState_title", @"Title shown when the Contacts section is empty, when you have not added any contact."); } return text; diff --git a/iMEGA/Chat/ShareLocationViewController.h b/iMEGA/Chat/ShareLocationViewController.h index 2a006e0d7d..2dbc136ccc 100644 --- a/iMEGA/Chat/ShareLocationViewController.h +++ b/iMEGA/Chat/ShareLocationViewController.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Chat/ShareLocationViewController.m b/iMEGA/Chat/ShareLocationViewController.m index e6ff4ac162..f6276605fa 100644 --- a/iMEGA/Chat/ShareLocationViewController.m +++ b/iMEGA/Chat/ShareLocationViewController.m @@ -1,4 +1,3 @@ - #import "ShareLocationViewController.h" #import @@ -11,6 +10,8 @@ #import "LocationSearchTableViewController.h" +@import MEGAL10nObjc; + @interface ShareLocationViewController () @property (weak, nonatomic) IBOutlet MKMapView *mapView; @@ -36,11 +37,11 @@ - (void)viewDidLoad { CLLocationManager *locationManager = CLLocationManager.alloc.init; if (!CLLocationManager.locationServicesEnabled || locationManager.authorizationStatus == kCLAuthorizationStatusDenied || locationManager.authorizationStatus == kCLAuthorizationStatusRestricted) { - NSString *message = [[NSLocalizedString(@"NSLocationWhenInUseUsageDescription", @"Location Usage Description. In order to protect user's privacy, Apple requires a specific string explaining why location will be accessed.") stringByAppendingString:@"\n\n"] stringByAppendingString:NSLocalizedString(@"Please go to the Privacy section in your device’s Setting. Enable Location Services and set MEGA to While Using the App or Always.", @"Hint shown to the users, when they want to use the Location Services but they are disabled or restricted for MEGA")]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Please allow access", @"Title of a dialog in which we request access to a specific permission, like the Location Services") message:message preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *notNow = [UIAlertAction actionWithTitle:NSLocalizedString(@"notNow", @"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.") style:UIAlertActionStyleCancel handler:nil]; + NSString *message = [[LocalizedString(@"NSLocationWhenInUseUsageDescription", @"Location Usage Description. In order to protect user's privacy, Apple requires a specific string explaining why location will be accessed.") stringByAppendingString:@"\n\n"] stringByAppendingString:LocalizedString(@"Please go to the Privacy section in your device’s Setting. Enable Location Services and set MEGA to While Using the App or Always.", @"Hint shown to the users, when they want to use the Location Services but they are disabled or restricted for MEGA")]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"Please allow access", @"Title of a dialog in which we request access to a specific permission, like the Location Services") message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *notNow = [UIAlertAction actionWithTitle:LocalizedString(@"notNow", @"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.") style:UIAlertActionStyleCancel handler:nil]; [alertController addAction:notNow]; - UIAlertAction *settings = [UIAlertAction actionWithTitle:NSLocalizedString(@"settingsTitle", @"Title of the Settings section") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertAction *settings = [UIAlertAction actionWithTitle:LocalizedString(@"settingsTitle", @"Title of the Settings section") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [UIApplication.sharedApplication openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil]; }]; [alertController addAction:settings]; @@ -67,7 +68,7 @@ - (void)viewDidLoad { gesture.numberOfTapsRequired = 1; [self.sendLocationView addGestureRecognizer:gesture]; - self.sendLocationLabel.text = NSLocalizedString(@"Send This Location", @"Title of the button to share a location in a chat."); + self.sendLocationLabel.text = LocalizedString(@"Send This Location", @"Title of the button to share a location in a chat."); UIView *separatorBetweenButtonsLayer = [UIView.alloc initWithFrame:CGRectMake(0, self.mapOptionsView.frame.size.height / 2, self.mapOptionsView.frame.size.width, 0.5)]; separatorBetweenButtonsLayer.backgroundColor = [UIColor mnz_separatorForTraitCollection:self.traitCollection]; @@ -87,7 +88,7 @@ - (void)viewDidLoad { self.searchController.searchBar.translucent = NO; self.navigationItem.searchController = self.searchController; - self.navigationItem.title = NSLocalizedString(@"Send Location", @"Alert title shown when the user opens a shared Geolocation for the first time from any client, we will show a confirmation dialog warning the user that he is now leaving the E2EE paradigm"); + self.navigationItem.title = LocalizedString(@"Send Location", @"Alert title shown when the user opens a shared Geolocation for the first time from any client, we will show a confirmation dialog warning the user that he is now leaving the E2EE paradigm"); [self updateAppearance]; } @@ -177,24 +178,24 @@ - (void)sendGeolocation:(UITapGestureRecognizer *)gesture { #pragma mark - IBAction - (IBAction)infoButtonTouchUpInside:(UIButton *)sender { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Map settings", @"Title of the alert that allows change between different maps: Standar, Satellite or Hybrid.") message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"Map settings", @"Title of the alert that allows change between different maps: Standar, Satellite or Hybrid.") message:nil preferredStyle:UIAlertControllerStyleActionSheet]; if (self.mapView.mapType != MKMapTypeStandard) { - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"chat.sendLocation.map.standard", @"Standard") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"chat.sendLocation.map.standard", @"Standard") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { self.mapView.mapType = MKMapTypeStandard; }]]; } if (self.mapView.mapType != MKMapTypeSatellite) { - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"chat.sendLocation.map.satellite", @"Satellite") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"chat.sendLocation.map.satellite", @"Satellite") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { self.mapView.mapType = MKMapTypeSatellite; }]]; } if (self.mapView.mapType != MKMapTypeHybrid) { - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"chat.sendLocation.map.hybrid", @"Hybrid") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"chat.sendLocation.map.hybrid", @"Hybrid") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { self.mapView.mapType = MKMapTypeHybrid; }]]; } - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; UIPopoverPresentationController *popoverPresentationController = [alertController popoverPresentationController]; if (popoverPresentationController) { @@ -259,7 +260,7 @@ - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { CLPlacemark *placemark = placemarks.firstObject; self.subtitleLabel.text = placemark.name; } else { - self.subtitleLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Accurate to %d meters", @"Label to give feedback to the user when he is going to share his location, indicating that it may not be the exact location."), (int)location.horizontalAccuracy]; + self.subtitleLabel.text = [NSString stringWithFormat:LocalizedString(@"Accurate to %d meters", @"Label to give feedback to the user when he is going to share his location, indicating that it may not be the exact location."), (int)location.horizontalAccuracy]; } }]; } diff --git a/iMEGA/ChatAttachedNodesViewController+Additions.swift b/iMEGA/ChatAttachedNodesViewController+Additions.swift new file mode 100644 index 0000000000..d970cc9c36 --- /dev/null +++ b/iMEGA/ChatAttachedNodesViewController+Additions.swift @@ -0,0 +1,11 @@ +import Foundation +import MEGAL10n + +extension ChatAttachedNodesViewController { + @objc func nodeCountTitle(_ count: Int) -> String { + guard count > 0 else { + return Strings.Localizable.selectTitle + } + return Strings.Localizable.General.Format.itemsSelected(count) + } +} diff --git a/iMEGA/ChatRoomAvatar/ChatRoomAvatarViewModel.swift b/iMEGA/ChatRoomAvatar/ChatRoomAvatarViewModel.swift index ab1d29358a..830795e759 100644 --- a/iMEGA/ChatRoomAvatar/ChatRoomAvatarViewModel.swift +++ b/iMEGA/ChatRoomAvatar/ChatRoomAvatarViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n final class ChatRoomAvatarViewModel: ObservableObject { let title: String diff --git a/iMEGA/ChatRoomAvatar/UserAvatarViewModel.swift b/iMEGA/ChatRoomAvatar/UserAvatarViewModel.swift index 187a70e056..51d98d997d 100644 --- a/iMEGA/ChatRoomAvatar/UserAvatarViewModel.swift +++ b/iMEGA/ChatRoomAvatar/UserAvatarViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n final class UserAvatarViewModel: ObservableObject { private let userId: MEGAHandle diff --git a/iMEGA/ChatRoomsListScene/ActiveCall/ActiveCallViewModel.swift b/iMEGA/ChatRoomsListScene/ActiveCall/ActiveCallViewModel.swift index 68409b059b..5898a8cae1 100644 --- a/iMEGA/ChatRoomsListScene/ActiveCall/ActiveCallViewModel.swift +++ b/iMEGA/ChatRoomsListScene/ActiveCall/ActiveCallViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n final class ActiveCallViewModel: ObservableObject { private var call: CallEntity @@ -14,7 +15,7 @@ final class ActiveCallViewModel: ObservableObject { private var baseDate = Date() - private var cancellableTimer: Cancellable? + private var cancellableTimer: (any Cancellable)? private let timer = Timer.publish(every: 1, on: .main, in: .common) diff --git a/iMEGA/ChatRoomsListScene/ChatRoomView.swift b/iMEGA/ChatRoomsListScene/ChatRoomView.swift index bbf8236940..b6051553f8 100644 --- a/iMEGA/ChatRoomsListScene/ChatRoomView.swift +++ b/iMEGA/ChatRoomsListScene/ChatRoomView.swift @@ -1,4 +1,6 @@ import MEGADomain +import MEGASwiftUI +import MEGAL10n import SwiftUI struct ChatRoomView: View { @@ -108,11 +110,8 @@ private struct ChatRoomContentView: View { .onTapGesture { viewModel.showDetails() } - .onAppear { - viewModel.onViewAppear() - } - .onDisappear { - viewModel.cancelLoading() + .taskForiOS14 { + await viewModel.loadChatRoomInfo() } } } diff --git a/iMEGA/ChatRoomsListScene/ChatRoomViewModel.swift b/iMEGA/ChatRoomsListScene/ChatRoomViewModel.swift index b76291a0f4..417eacb206 100644 --- a/iMEGA/ChatRoomsListScene/ChatRoomViewModel.swift +++ b/iMEGA/ChatRoomsListScene/ChatRoomViewModel.swift @@ -1,7 +1,9 @@ import Combine import Foundation import MEGADomain +import MEGAL10n import MEGAPermissions +import MEGAPresentation import MEGASwift import MEGAUI @@ -32,9 +34,7 @@ final class ChatRoomViewModel: ObservableObject, Identifiable, CallInProgressTim private(set) var displayDateString: String? private var subscriptions = Set() - private var loadingChatRoomInfoTask: Task? - - private var isViewOnScreen = false + private var loadingChatRoomInfoSubscription: AnyCancellable? private var searchString = "" @@ -48,7 +48,7 @@ final class ChatRoomViewModel: ObservableObject, Identifiable, CallInProgressTim var callDurationCapturedTime: TimeInterval? var timerSubscription: AnyCancellable? let permissionHandler: any DevicePermissionsHandling - + init(chatListItem: ChatListItemEntity, router: some ChatRoomsListRouting, chatRoomUseCase: any ChatRoomUseCaseProtocol, @@ -113,19 +113,29 @@ final class ChatRoomViewModel: ObservableObject, Identifiable, CallInProgressTim } // MARK: - Interface methods - - func onViewAppear() { - isViewOnScreen = true - - loadChatRoomInfo() - } - - func cancelLoading() { - isViewOnScreen = false - - cancelChatRoomInfoTask() + + func loadChatRoomInfo() async { + let chatId = chatListItem.chatId + + guard !Task.isCancelled else { + MEGALogDebug("Task cancelled for \(chatId) - won't update description") + return + } + + do { + try await updateDescription() + } catch { + MEGALogDebug("Unable to load description for \(chatId) - \(error.localizedDescription)") + } + + do { + try Task.checkCancellation() + await sendObjectChangeNotification() + } catch { + MEGALogDebug("Task cancelled for \(chatId) - won't send object change notification") + } } - + func chatStatusColor(forChatStatus chatStatus: ChatStatusEntity) -> UIColor? { switch chatStatus { case .online: @@ -296,39 +306,7 @@ final class ChatRoomViewModel: ObservableObject, Identifiable, CallInProgressTim return options } - - private func cancelChatRoomInfoTask() { - loadingChatRoomInfoTask?.cancel() - loadingChatRoomInfoTask = nil - } - - private func loadChatRoomInfo() { - loadingChatRoomInfoTask = Task { [weak self] in - guard let self else { return } - - let chatId = chatListItem.chatId - - defer { - cancelChatRoomInfoTask() - } - - do { - try await self.updateDescription() - } catch { - MEGALogDebug("Unable to load description for \(chatId) - \(error.localizedDescription)") - } - - guard self.isViewOnScreen else { return } - - do { - try Task.checkCancellation() - await sendObjectChangeNotification() - } catch { - MEGALogDebug("Task cancelled for \(chatId)") - } - } - } - + private func loadChatRoomSearchString() { Task { [weak self] in guard let self, let chatRoom = self.chatRoomUseCase.chatRoom(forChatId: self.chatListItem.chatId) else { @@ -755,11 +733,21 @@ final class ChatRoomViewModel: ObservableObject, Identifiable, CallInProgressTim return } - startOrJoinCall() + if chatRoomUseCase.shouldOpenWaitingRoom(forChatId: chatListItem.chatId) + && DIContainer.featureFlagProvider.isFeatureFlagEnabled(for: .waitingRoom) { + openWaitingRoom() + } else { + startOrJoinCall() + } } } - func startOrJoinCall() { + private func openWaitingRoom() { + guard let scheduledMeeting = scheduledMeetingUseCase.scheduledMeetingsByChat(chatId: chatListItem.chatId).first else { return } + router.presentWaitingRoom(for: scheduledMeeting) + } + + private func startOrJoinCall() { guard let chatRoom = chatRoomUseCase.chatRoom(forChatId: chatListItem.chatId) else { MEGALogError("Not able to fetch chat room for start or join call") return @@ -769,7 +757,11 @@ final class ChatRoomViewModel: ObservableObject, Identifiable, CallInProgressTim prepareAndShowCallUI(for: call, in: chatRoom) } else { if let scheduledMeeting = scheduledMeetingUseCase.scheduledMeetingsByChat(chatId: chatListItem.chatId).first { - startMeetingCallNoRinging(for: scheduledMeeting, in: chatRoom) + if chatRoom.isWaitingRoomEnabled { + startMeetingInWaitingRoomChat(for: scheduledMeeting, in: chatRoom) + } else { + startMeetingCallNoRinging(for: scheduledMeeting, in: chatRoom) + } } else { startCall(in: chatRoom) } @@ -810,6 +802,24 @@ final class ChatRoomViewModel: ObservableObject, Identifiable, CallInProgressTim } } + private func startMeetingInWaitingRoomChat(for scheduledMeeting: ScheduledMeetingEntity, in chatRoom: ChatRoomEntity) { + callUseCase.startMeetingInWaitingRoomChat(for: scheduledMeeting, enableVideo: false, enableAudio: true) { [weak self] result in + guard let self else { return } + switch result { + case .success(let call): + prepareAndShowCallUI(for: call, in: chatRoom) + case .failure(let error): + switch error { + case .tooManyParticipants: + router.showErrorMessage(Strings.Localizable.Error.noMoreParticipantsAreAllowedInThisGroupCall) + default: + router.showErrorMessage(Strings.Localizable.somethingWentWrong) + MEGALogError("Not able to start scheduled meeting call") + } + } + } + } + private func prepareAndShowCallUI(for call: CallEntity, in chatRoom: ChatRoomEntity) { audioSessionUseCase.enableLoudSpeaker() router.openCallView(for: call, in: chatRoom) diff --git a/iMEGA/ChatRoomsListScene/ChatRoomsListRouter.swift b/iMEGA/ChatRoomsListScene/ChatRoomsListRouter.swift index 978fb22772..0dd699850e 100644 --- a/iMEGA/ChatRoomsListScene/ChatRoomsListRouter.swift +++ b/iMEGA/ChatRoomsListScene/ChatRoomsListRouter.swift @@ -1,5 +1,7 @@ import MEGADomain +import MEGAL10n import MEGAPermissions +import MEGARepo import MEGASDKRepo final class ChatRoomsListRouter: ChatRoomsListRouting { @@ -17,12 +19,7 @@ final class ChatRoomsListRouter: ChatRoomsListRouting { let permissionHandler = DevicePermissionsHandler.makeHandler() let viewModel = ChatRoomsListViewModel( router: self, - chatUseCase: ChatUseCase( - chatRepo: ChatRepository( - sdk: MEGASdkManager.sharedMEGASdk(), - chatSDK: MEGASdkManager.sharedMEGAChatSdk() - ) - ), + chatUseCase: ChatUseCase(chatRepo: ChatRepository(sdk: .shared, chatSDK: .shared)), chatRoomUseCase: chatRoomUseCase, contactsUseCase: ContactsUseCase(repository: ContactsRepository()), networkMonitorUseCase: NetworkMonitorUseCase(repo: NetworkMonitorRepository()), @@ -50,7 +47,7 @@ final class ChatRoomsListRouter: ChatRoomsListRouting { navigationController?.pushViewController(controller, animated: true) } - func presentMeetingAlreayExists() { + func presentMeetingAlreadyExists() { guard let navigationController else { return } MeetingAlreadyExistsAlert.show(presenter: navigationController) } @@ -82,6 +79,10 @@ final class ChatRoomsListRouter: ChatRoomsListRouting { ) ScheduleMeetingRouter(presenter: navigationController, viewConfiguration: viewConfiguration).start() } + + func presentWaitingRoom(for scheduledMeeting: ScheduledMeetingEntity) { + WaitingRoomViewRouter(presenter: chatRoomsListViewController, scheduledMeeting: scheduledMeeting).start() + } func showDetails(forChatId chatId: HandleEntity, unreadMessagesCount: Int) { guard let navigationController, let chatViewController = ChatViewController(chatId: chatId) else { return } @@ -235,7 +236,7 @@ final class ChatRoomsListRouter: ChatRoomsListRouting { return } - let isSpeakerEnabled = AVAudioSession.sharedInstance().mnz_isOutputEqual(toPortType: .builtInSpeaker) + let isSpeakerEnabled = AVAudioSession.sharedInstance().isOutputEqualToPortType(.builtInSpeaker) MeetingContainerRouter(presenter: navigationController, chatRoom: chatRoom, call: call, diff --git a/iMEGA/ChatRoomsListScene/ChatRoomsListRouting.swift b/iMEGA/ChatRoomsListScene/ChatRoomsListRouting.swift index b3e11731bf..815c2228d0 100644 --- a/iMEGA/ChatRoomsListScene/ChatRoomsListRouting.swift +++ b/iMEGA/ChatRoomsListScene/ChatRoomsListRouting.swift @@ -3,10 +3,11 @@ import MEGADomain protocol ChatRoomsListRouting { var navigationController: UINavigationController? { get } func presentStartConversation() - func presentMeetingAlreayExists() + func presentMeetingAlreadyExists() func presentCreateMeeting() func presentEnterMeeting() func presentScheduleMeeting() + func presentWaitingRoom(for scheduledMeeting: ScheduledMeetingEntity) func showInviteContactScreen() func showDetails(forChatId chatId: HandleEntity, unreadMessagesCount: Int) func openChatRoom(withChatId chatId: ChatIdEntity, publicLink: String?, unreadMessageCount: Int) diff --git a/iMEGA/ChatRoomsListScene/ChatRoomsListView.swift b/iMEGA/ChatRoomsListScene/ChatRoomsListView.swift index 037830feb8..d6462ee700 100644 --- a/iMEGA/ChatRoomsListScene/ChatRoomsListView.swift +++ b/iMEGA/ChatRoomsListScene/ChatRoomsListView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGASwiftUI import SwiftUI diff --git a/iMEGA/ChatRoomsListScene/ChatRoomsListViewController.swift b/iMEGA/ChatRoomsListScene/ChatRoomsListViewController.swift index 544f477943..27abc47566 100644 --- a/iMEGA/ChatRoomsListScene/ChatRoomsListViewController.swift +++ b/iMEGA/ChatRoomsListScene/ChatRoomsListViewController.swift @@ -1,5 +1,6 @@ import Combine import MEGAChatSdk +import MEGAL10n import MEGAUIKit import SwiftUI diff --git a/iMEGA/ChatRoomsListScene/ChatRoomsListViewModel.swift b/iMEGA/ChatRoomsListScene/ChatRoomsListViewModel.swift index 5538477139..0858dcbd6a 100644 --- a/iMEGA/ChatRoomsListScene/ChatRoomsListViewModel.swift +++ b/iMEGA/ChatRoomsListScene/ChatRoomsListViewModel.swift @@ -2,6 +2,7 @@ import ChatRepo import Combine import Foundation import MEGADomain +import MEGAL10n import MEGAPermissions import MEGAPresentation import MEGARepo @@ -607,7 +608,7 @@ final class ChatRoomsListViewModel: ObservableObject { userStoreRepo: UserStoreRepository(store: MEGAStore.shareInstance())) let megaHandleUseCase = MEGAHandleUseCase(repo: MEGAHandleRepository.newRepo) let userImageUseCase = UserImageUseCase( - userImageRepo: UserImageRepository(sdk: MEGASdkManager.sharedMEGASdk()), + userImageRepo: UserImageRepository(sdk: .shared), userStoreRepo: UserStoreRepository(store: MEGAStore.shareInstance()), thumbnailRepo: ThumbnailRepository.newRepo, fileSystemRepo: FileSystemRepository.newRepo @@ -622,7 +623,7 @@ final class ChatRoomsListViewModel: ObservableObject { chatUseCase: chatUseCase, accountUseCase: accountUseCase, megaHandleUseCase: megaHandleUseCase, - callUseCase: CallUseCase(repository: CallRepository(chatSdk: MEGASdkManager.sharedMEGAChatSdk(), callActionManager: CallActionManager.shared)), + callUseCase: CallUseCase(repository: CallRepository(chatSdk: .shared, callActionManager: CallActionManager.shared)), audioSessionUseCase: AudioSessionUseCase(audioSessionRepository: AudioSessionRepository(audioSession: AVAudioSession(), callActionManager: CallActionManager.shared)), scheduledMeetingUseCase: scheduledMeetingUseCase, chatNotificationControl: chatNotificationControl, permissionHandler: permissionHandler ) @@ -637,7 +638,7 @@ final class ChatRoomsListViewModel: ObservableObject { userStoreRepo: UserStoreRepository(store: MEGAStore.shareInstance())) let megaHandleUseCase = MEGAHandleUseCase(repo: MEGAHandleRepository.newRepo) let userImageUseCase = UserImageUseCase( - userImageRepo: UserImageRepository(sdk: MEGASdkManager.sharedMEGASdk()), + userImageRepo: UserImageRepository(sdk: .shared), userStoreRepo: UserStoreRepository(store: MEGAStore.shareInstance()), thumbnailRepo: ThumbnailRepository.newRepo, fileSystemRepo: FileSystemRepository.newRepo @@ -652,7 +653,7 @@ final class ChatRoomsListViewModel: ObservableObject { userImageUseCase: userImageUseCase, chatUseCase: chatUseCase, accountUseCase: accountUseCase, - callUseCase: CallUseCase(repository: CallRepository(chatSdk: MEGASdkManager.sharedMEGAChatSdk(), callActionManager: CallActionManager.shared)), + callUseCase: CallUseCase(repository: CallRepository(chatSdk: .shared, callActionManager: CallActionManager.shared)), audioSessionUseCase: AudioSessionUseCase(audioSessionRepository: AudioSessionRepository(audioSession: AVAudioSession(), callActionManager: CallActionManager.shared)), scheduledMeetingUseCase: scheduledMeetingUseCase, megaHandleUseCase: megaHandleUseCase, @@ -756,7 +757,7 @@ final class ChatRoomsListViewModel: ObservableObject { activeCallViewModel = ActiveCallViewModel( call: call, router: router, - activeCallUseCase: ActiveCallUseCase(callRepository: CallRepository(chatSdk: MEGASdkManager.sharedMEGAChatSdk(), callActionManager: CallActionManager.shared)), + activeCallUseCase: ActiveCallUseCase(callRepository: CallRepository(chatSdk: .shared, callActionManager: CallActionManager.shared)), chatRoomUseCase: chatRoomUseCase ) } else { @@ -859,7 +860,7 @@ extension ChatRoomsListViewModel: ChatMenuDelegate { extension ChatRoomsListViewModel: MeetingContextMenuDelegate { func meetingContextMenu(didSelect action: MeetingActionEntity) { if chatUseCase.existsActiveCall() { - router.presentMeetingAlreayExists() + router.presentMeetingAlreadyExists() return } diff --git a/iMEGA/ChatRoomsListScene/Data Model/ChatRoomContextMenuOption.swift b/iMEGA/ChatRoomsListScene/Data Model/ChatRoomContextMenuOption.swift index 852dd52da9..855603bf62 100644 --- a/iMEGA/ChatRoomsListScene/Data Model/ChatRoomContextMenuOption.swift +++ b/iMEGA/ChatRoomsListScene/Data Model/ChatRoomContextMenuOption.swift @@ -1,4 +1,3 @@ - struct ChatRoomContextMenuOption: Identifiable, Hashable { let title: String let imageName: String diff --git a/iMEGA/ChatRoomsListScene/Data Model/ChatRoomHybridDescriptionViewState.swift b/iMEGA/ChatRoomsListScene/Data Model/ChatRoomHybridDescriptionViewState.swift index f8973669c5..8916811b2d 100644 --- a/iMEGA/ChatRoomsListScene/Data Model/ChatRoomHybridDescriptionViewState.swift +++ b/iMEGA/ChatRoomsListScene/Data Model/ChatRoomHybridDescriptionViewState.swift @@ -1,4 +1,3 @@ - struct ChatRoomHybridDescriptionViewState { let sender: String? let image: UIImage diff --git a/iMEGA/ChatRoomsListScene/Data Model/ChatRoomsEmptyViewState.swift b/iMEGA/ChatRoomsListScene/Data Model/ChatRoomsEmptyViewState.swift index f31b638a77..0620ee0dd9 100644 --- a/iMEGA/ChatRoomsListScene/Data Model/ChatRoomsEmptyViewState.swift +++ b/iMEGA/ChatRoomsListScene/Data Model/ChatRoomsEmptyViewState.swift @@ -1,4 +1,3 @@ - struct ChatRoomsEmptyViewState { let contactsOnMega: ChatRoomsTopRowViewState? let archivedChats: ChatRoomsTopRowViewState? diff --git a/iMEGA/ChatRoomsListScene/Data Model/ChatRoomsTopRowViewState.swift b/iMEGA/ChatRoomsListScene/Data Model/ChatRoomsTopRowViewState.swift index 43cabd0eca..e6dbfa8aee 100644 --- a/iMEGA/ChatRoomsListScene/Data Model/ChatRoomsTopRowViewState.swift +++ b/iMEGA/ChatRoomsListScene/Data Model/ChatRoomsTopRowViewState.swift @@ -1,4 +1,3 @@ - struct ChatRoomsTopRowViewState { let image: UIImage let imageTintColor: UIColor? diff --git a/iMEGA/ChatRoomsListScene/FutureMeetingRoomView.swift b/iMEGA/ChatRoomsListScene/FutureMeetingRoomView.swift index 84b91e4c98..14c6196fe5 100644 --- a/iMEGA/ChatRoomsListScene/FutureMeetingRoomView.swift +++ b/iMEGA/ChatRoomsListScene/FutureMeetingRoomView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import SwiftUI struct FutureMeetingRoomView: View { diff --git a/iMEGA/ChatRoomsListScene/FutureMeetingRoomViewModel.swift b/iMEGA/ChatRoomsListScene/FutureMeetingRoomViewModel.swift index 00265c290e..89def04329 100644 --- a/iMEGA/ChatRoomsListScene/FutureMeetingRoomViewModel.swift +++ b/iMEGA/ChatRoomsListScene/FutureMeetingRoomViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import MEGAPresentation final class FutureMeetingRoomViewModel: ObservableObject, Identifiable, CallInProgressTimeReporting { @@ -248,7 +249,7 @@ final class FutureMeetingRoomViewModel: ObservableObject, Identifiable, CallInPr scheduledMeeting.cancelled = true _ = try await scheduledMeetingUseCase.updateScheduleMeeting(scheduledMeeting) if !chatHasMessages { - archiveChatRoom() + archiveChatRoom(afterCancelMeeting: true) } else { router.showSuccessMessage(Strings.Localizable.Meetings.Scheduled.CancelAlert.Success.withMessages) } @@ -259,12 +260,15 @@ final class FutureMeetingRoomViewModel: ObservableObject, Identifiable, CallInPr } } - private func archiveChatRoom() { + private func archiveChatRoom(afterCancelMeeting: Bool) { Task { do { guard let chatRoom = self.chatRoomUseCase.chatRoom(forChatId: self.scheduledMeeting.chatId) else { return } + try await Task.sleep(nanoseconds: 2_000_000_000) // This is a temporal workaround until API fixes that some management messages (like this one, cancelled meeting) don't unarchive chats MEET-2928 _ = try await chatRoomUseCase.archive(true, chatRoom: chatRoom) - router.showSuccessMessage(Strings.Localizable.Meetings.Scheduled.CancelAlert.Success.withoutMessages) + if afterCancelMeeting { + router.showSuccessMessage(Strings.Localizable.Meetings.Scheduled.CancelAlert.Success.withoutMessages) + } } catch { router.showErrorMessage(Strings.Localizable.somethingWentWrong) MEGALogError("Failed to archive chat") @@ -291,10 +295,19 @@ final class FutureMeetingRoomViewModel: ObservableObject, Identifiable, CallInPr return } - startOrJoinCall() + if chatRoomUseCase.shouldOpenWaitingRoom(forChatId: scheduledMeeting.chatId) + && DIContainer.featureFlagProvider.isFeatureFlagEnabled(for: .waitingRoom) { + openWaitingRoom() + } else { + startOrJoinCall() + } } } + private func openWaitingRoom() { + router.presentWaitingRoom(for: scheduledMeeting) + } + func startOrJoinCall() { guard let chatRoom = chatRoomUseCase.chatRoom(forChatId: scheduledMeeting.chatId) else { MEGALogError("Not able to fetch chat room for start or join call") @@ -304,7 +317,11 @@ final class FutureMeetingRoomViewModel: ObservableObject, Identifiable, CallInPr if existsInProgressCallInChatRoom { joinCall(in: chatRoom) } else { - startMeetingCallNoRinging(in: chatRoom) + if chatRoom.isWaitingRoomEnabled { + startMeetingInWaitingRoomChat(in: chatRoom) + } else { + startMeetingCallNoRinging(in: chatRoom) + } } } @@ -347,6 +364,24 @@ final class FutureMeetingRoomViewModel: ObservableObject, Identifiable, CallInPr } } + private func startMeetingInWaitingRoomChat(in chatRoom: ChatRoomEntity) { + callUseCase.startMeetingInWaitingRoomChat(for: scheduledMeeting, enableVideo: false, enableAudio: true) { [weak self] result in + guard let self else { return } + switch result { + case .success(let call): + prepareAndShowCallUI(for: call, in: chatRoom) + case .failure(let error): + switch error { + case .tooManyParticipants: + router.showErrorMessage(Strings.Localizable.Error.noMoreParticipantsAreAllowedInThisGroupCall) + default: + router.showErrorMessage(Strings.Localizable.somethingWentWrong) + MEGALogError("Not able to start scheduled meeting call") + } + } + } + } + private func prepareAndShowCallUI(for call: CallEntity, in chatRoom: ChatRoomEntity) { audioSessionUseCase.enableLoudSpeaker() router.openCallView(for: call, in: chatRoom) @@ -445,7 +480,7 @@ extension FutureMeetingRoomViewModel { imageName: Asset.Images.Chat.ContextualMenu.archiveChatMenu.name ) { [weak self] in guard let self else { return } - archiveChatRoom() + archiveChatRoom(afterCancelMeeting: false) } } diff --git a/iMEGA/ChatRoomsListScene/Views/ChatTabsSelectorView.swift b/iMEGA/ChatRoomsListScene/Views/ChatTabsSelectorView.swift index eda285f93f..291e777084 100644 --- a/iMEGA/ChatRoomsListScene/Views/ChatTabsSelectorView.swift +++ b/iMEGA/ChatRoomsListScene/Views/ChatTabsSelectorView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ChatTabsSelectorView: View { diff --git a/iMEGA/ChatRoomsViewController+Additions.swift b/iMEGA/ChatRoomsViewController+Additions.swift index a16f8962c4..2bd2a336a9 100644 --- a/iMEGA/ChatRoomsViewController+Additions.swift +++ b/iMEGA/ChatRoomsViewController+Additions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPermissions import MEGASDKRepo @@ -112,7 +113,7 @@ extension ChatRoomsViewController: ChatMenuDelegate, MeetingContextMenuDelegate } func meetingContextMenu(didSelect action: MeetingActionEntity) { - if MEGASdkManager.sharedMEGAChatSdk().mnz_existsActiveCall { + if MEGAChatSdk.shared.mnz_existsActiveCall { MeetingAlreadyExistsAlert.show(presenter: self) return } diff --git a/iMEGA/ChatRoomsViewController+MyAvatar.swift b/iMEGA/ChatRoomsViewController+MyAvatar.swift index b02000c57e..34bce6f7e4 100644 --- a/iMEGA/ChatRoomsViewController+MyAvatar.swift +++ b/iMEGA/ChatRoomsViewController+MyAvatar.swift @@ -1,4 +1,3 @@ - extension ChatRoomsViewController: MyAvatarPresenterProtocol { func setupMyAvatar(barButton: UIBarButtonItem) { self.navigationItem.leftBarButtonItem = barButton diff --git a/iMEGA/ChatRoomsViewController.swift b/iMEGA/ChatRoomsViewController.swift index d0c665dbd8..c36c1195cb 100644 --- a/iMEGA/ChatRoomsViewController.swift +++ b/iMEGA/ChatRoomsViewController.swift @@ -1,13 +1,14 @@ import MEGADomain +import MEGAL10n import UIKit extension ChatRoomsViewController { @objc func joinActiveCall(withChatRoom chatRoom: MEGAChatRoom) { - guard let call = MEGASdkManager.sharedMEGAChatSdk().chatCall(forChatId: chatRoom.chatId) else { + guard let call = MEGAChatSdk.shared.chatCall(forChatId: chatRoom.chatId) else { return } - let isSpeakerEnabled = AVAudioSession.sharedInstance().mnz_isOutputEqual(toPortType: .builtInSpeaker) + let isSpeakerEnabled = AVAudioSession.sharedInstance().isOutputEqualToPortType(.builtInSpeaker) MeetingContainerRouter(presenter: self, chatRoom: chatRoom.toChatRoomEntity(), call: call.toCallEntity(), diff --git a/iMEGA/ChatSharedItems/ChatSharedItemTableViewCell.swift b/iMEGA/ChatSharedItems/ChatSharedItemTableViewCell.swift index d4cd07da1e..291a6b949e 100644 --- a/iMEGA/ChatSharedItems/ChatSharedItemTableViewCell.swift +++ b/iMEGA/ChatSharedItems/ChatSharedItemTableViewCell.swift @@ -1,4 +1,3 @@ - import MEGADomain import MEGASDKRepo import UIKit @@ -33,14 +32,14 @@ class ChatSharedItemTableViewCell: UITableViewCell { nameLabel.text = node.name ownerNameLabel.text = chatRoom.userDisplayName(forUserHandle: ownerHandle) ownerNameLabel.textColor = .mnz_subtitles(for: traitCollection) - infoLabel.text = Helper.sizeAndModicationDate(for: node, api: MEGASdkManager.sharedMEGASdk()) + infoLabel.text = Helper.sizeAndModicationDate(for: node, api: .shared) infoLabel.textColor = .mnz_subtitles(for: traitCollection) if node.hasThumbnail() { let thumbnailFilePath = Helper.path(for: node, inSharedSandboxCacheDirectory: "thumbnailsV3") if FileManager.default.fileExists(atPath: thumbnailFilePath) { thumbnailImage.image = UIImage(contentsOfFile: thumbnailFilePath) } else { - MEGASdkManager.sharedMEGASdk().getThumbnailNode(node, destinationFilePath: thumbnailFilePath, delegate: RequestDelegate { [weak self] result in + MEGASdk.shared.getThumbnailNode(node, destinationFilePath: thumbnailFilePath, delegate: RequestDelegate { [weak self] result in if case .success(let request) = result, request.nodeHandle == node.handle { self?.thumbnailImage.image = UIImage(contentsOfFile: request.file) } diff --git a/iMEGA/ChatSharedItems/ChatSharedItemsViewController.swift b/iMEGA/ChatSharedItems/ChatSharedItemsViewController.swift index 560507f0c3..8dae1462e4 100644 --- a/iMEGA/ChatSharedItems/ChatSharedItemsViewController.swift +++ b/iMEGA/ChatSharedItems/ChatSharedItemsViewController.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo import UIKit @@ -52,14 +53,14 @@ class ChatSharedItemsViewController: UIViewController { tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: CGFloat.leastNormalMagnitude)) tableView.allowsMultipleSelectionDuringEditing = true - MEGASdkManager.sharedMEGAChatSdk().openNodeHistory(forChat: chatRoom.chatId, delegate: self) + MEGAChatSdk.shared.openNodeHistory(forChat: chatRoom.chatId, delegate: self) updateAppearance() loadMoreFiles() } override func viewWillDisappear(_ animated: Bool) { - MEGASdkManager.sharedMEGAChatSdk().closeNodeHistory(forChat: chatRoom.chatId, delegate: self) + MEGAChatSdk.shared.closeNodeHistory(forChat: chatRoom.chatId, delegate: self) super.viewWillDisappear(animated) } @@ -183,15 +184,11 @@ class ChatSharedItemsViewController: UIViewController { title = Strings.Localizable.selectTitle return } - if selectedCount == 1 { - title = Strings.Localizable.oneItemSelected(selectedCount) - } else { - title = Strings.Localizable.itemsSelected(selectedCount) - } + title = Strings.Localizable.General.Format.itemsSelected(selectedCount) } private func loadMoreFiles() { - let source = MEGASdkManager.sharedMEGAChatSdk().loadAttachments(forChat: chatRoom.chatId, count: 16) + let source = MEGAChatSdk.shared.loadAttachments(forChat: chatRoom.chatId, count: 16) switch source { case .error: @@ -249,14 +246,14 @@ class ChatSharedItemsViewController: UIViewController { continue } let handle = messagesArray[indexPath.row].userHandle - if MEGASdkManager.sharedMEGAChatSdk().userFullnameFromCache(byUserHandle: handle) == nil && !requestedParticipantsMutableSet.contains(handle) { + if MEGAChatSdk.shared.userFullnameFromCache(byUserHandle: handle) == nil && !requestedParticipantsMutableSet.contains(handle) { userHandles.append(handle) requestedParticipantsMutableSet.insert(handle) } } if userHandles.isNotEmpty { - MEGASdkManager.sharedMEGAChatSdk().loadUserAttributes(forChatId: chatRoom.chatId, usersHandles: userHandles as [NSNumber], delegate: self) + MEGAChatSdk.shared.loadUserAttributes(forChatId: chatRoom.chatId, usersHandles: userHandles as [NSNumber], delegate: self) } } } @@ -401,7 +398,7 @@ extension ChatSharedItemsViewController: UITableViewDelegate { if name.fileExtensionGroup.isVisualMedia { if chatRoom.isPreview { - guard let authNode = MEGASdkManager.sharedMEGASdk().authorizeChatNode(node, cauth: chatRoom.authorizationToken) else { return } + guard let authNode = MEGASdk.shared.authorizeChatNode(node, cauth: chatRoom.authorizationToken) else { return } nodes.add(authNode) } else { nodes.add(node) @@ -414,12 +411,12 @@ extension ChatSharedItemsViewController: UITableViewDelegate { return } - let photoBrowserVC = MEGAPhotoBrowserViewController.photoBrowser(withMediaNodes: nodes, api: MEGASdkManager.sharedMEGASdk(), displayMode: .chatSharedFiles, presenting: selectedNode) + let photoBrowserVC = MEGAPhotoBrowserViewController.photoBrowser(withMediaNodes: nodes, api: MEGASdk.shared, displayMode: .chatSharedFiles, presenting: selectedNode) photoBrowserVC.configureMediaAttachment(forMessageId: message.messageId, inChatId: chatRoom.chatId, messagesIds: messagesIdsArray) navigationController?.present(photoBrowserVC, animated: true, completion: nil) } else { if chatRoom.isPreview { - guard let authNode = MEGASdkManager.sharedMEGASdk().authorizeChatNode(selectedNode, cauth: chatRoom.authorizationToken) else { return } + guard let authNode = MEGASdk.shared.authorizeChatNode(selectedNode, cauth: chatRoom.authorizationToken) else { return } authNode.mnz_open(in: navigationController, folderLink: false, fileLink: nil, messageId: nil, chatId: nil, allNodes: nil) } else { selectedNode.mnz_open(in: navigationController, folderLink: false, fileLink: nil, messageId: nil, chatId: nil, allNodes: nil) @@ -487,7 +484,7 @@ extension ChatSharedItemsViewController: NodeActionViewControllerDelegate { return } - let saveMediaUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: MEGASdkManager.sharedMEGASdk()), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) + let saveMediaUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: MEGASdk.shared), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) TransfersWidgetViewController.sharedTransfer().bringProgressToFrontKeyWindowIfNeeded() saveMediaUseCase.saveToPhotosChatNode(handle: node.handle, messageId: message.messageId, chatId: chatRoom.chatId, completion: { result in diff --git a/iMEGA/ChatStateListener.swift b/iMEGA/ChatStateListener.swift index 176c926576..ff9a7f1d2d 100644 --- a/iMEGA/ChatStateListener.swift +++ b/iMEGA/ChatStateListener.swift @@ -1,7 +1,7 @@ import MEGADomain final class ChatStateListener: NSObject, MEGAChatDelegate { - private var continuation: CheckedContinuation? + private var continuation: CheckedContinuation? let chatId: ChatIdEntity let connectionState: ChatConnectionStatus diff --git a/iMEGA/ChatUploader.swift b/iMEGA/ChatUploader.swift index 12b1927301..99fa56d923 100644 --- a/iMEGA/ChatUploader.swift +++ b/iMEGA/ChatUploader.swift @@ -1,4 +1,3 @@ - @objc class ChatUploader: NSObject { @objc static let sharedInstance = ChatUploader() @@ -11,7 +10,7 @@ @objc func setup() { isDatabaseCleanupTaskCompleted = false - MEGASdkManager.sharedMEGASdk().add(self) + MEGASdk.shared.add(self) } @objc func upload(image: UIImage, chatRoomId: UInt64) { @@ -64,12 +63,12 @@ context: context) MEGALogInfo("[ChatUploader] SDK upload started for File path \(filepath)") - MEGASdkManager.sharedMEGASdk().startUploadForChat(withLocalPath: filepath, - parent: parentNode, - appData: appData, - isSourceTemporary: isSourceTemporary, - fileName: nil, - delegate: delegate) + MEGASdk.shared.startUploadForChat(withLocalPath: filepath, + parent: parentNode, + appData: appData, + isSourceTemporary: isSourceTemporary, + fileName: nil, + delegate: delegate) } } @@ -85,7 +84,7 @@ guard let context = store.stack.newBackgroundContext() else { return } context.performAndWait { - let transferList = MEGASdkManager.sharedMEGASdk().transfers + let transferList = MEGASdk.shared.transfers MEGALogDebug("[ChatUploader] transfer list count : \(transferList.size.intValue)") let sdkTransfers = (0.. @interface SendToChatActivity : UIActivity diff --git a/iMEGA/Cloud drive/Activities/SendToChatActivity.m b/iMEGA/Cloud drive/Activities/SendToChatActivity.m index 4388cff169..35969a1c50 100644 --- a/iMEGA/Cloud drive/Activities/SendToChatActivity.m +++ b/iMEGA/Cloud drive/Activities/SendToChatActivity.m @@ -1,9 +1,10 @@ - #import "SendToChatActivity.h" #import "MEGANavigationController.h" #import "SendToViewController.h" +@import MEGAL10nObjc; + @interface SendToChatActivity () @property (strong, nonatomic) NSArray *nodes; @@ -36,7 +37,7 @@ - (NSString *)activityType { } - (NSString *)activityTitle { - return NSLocalizedString(@"general.sendToChat", @""); + return LocalizedString(@"general.sendToChat", @""); } - (UIImage *)activityImage { diff --git a/iMEGA/Cloud drive/BrowserViewController+Additions.swift b/iMEGA/Cloud drive/BrowserViewController+Additions.swift index f29b739a92..f1f6de7bdc 100644 --- a/iMEGA/Cloud drive/BrowserViewController+Additions.swift +++ b/iMEGA/Cloud drive/BrowserViewController+Additions.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGAUIKit extension BrowserViewController { @@ -50,7 +51,7 @@ extension BrowserViewController { return (Strings.Localizable.selectDestination, false) } else { // not sure what to do with this, it's not localized - return (NSLocalizedString("MEGA", comment: ""), false) + return (Strings.localized("MEGA", comment: ""), false) } } else { if isChildBrowserFromIncoming { @@ -77,4 +78,11 @@ extension BrowserViewController { let titleConfig = navigationBarTitleConfig() updateTitle(title: titleConfig.copy, shouldPlaceInTitleView: titleConfig.renderInTitleView) } + + @objc func prompt(forSelectedCount count: Int) -> String { + guard count > 0 else { + return Strings.Localizable.selectTitle + } + return Strings.Localizable.General.Format.itemsSelected(count) + } } diff --git a/iMEGA/Cloud drive/BrowserViewController.m b/iMEGA/Cloud drive/BrowserViewController.m index 55c08a1afe..4b3bdbd8fd 100644 --- a/iMEGA/Cloud drive/BrowserViewController.m +++ b/iMEGA/Cloud drive/BrowserViewController.m @@ -23,6 +23,7 @@ #import "NodeTableViewCell.h" @import DZNEmptyDataSet; +@import MEGAL10nObjc; @import MEGAUIKit; @import MEGASDKRepo; @@ -159,7 +160,7 @@ - (void)setupBrowser { case BrowserActionCopy: { [self setupDefaultElements]; - self.toolBarCopyBarButtonItem.title = NSLocalizedString(@"cloudDrive.browser.paste", @"List option shown on the details of a file or folder after the user has copied it"); + self.toolBarCopyBarButtonItem.title = LocalizedString(@"cloudDrive.browser.paste", @"List option shown on the details of a file or folder after the user has copied it"); [self setToolbarItems:@[self.toolBarNewFolderBarButtonItem, flexibleItem, self.toolBarCopyBarButtonItem]]; break; } @@ -167,7 +168,7 @@ - (void)setupBrowser { case BrowserActionMove: { [self setupDefaultElements]; - self.toolBarMoveBarButtonItem.title = NSLocalizedString(@"move", @"Title for the action that allows you to move a file or folder"); + self.toolBarMoveBarButtonItem.title = LocalizedString(@"move", @"Title for the action that allows you to move a file or folder"); [self setToolbarItems:@[self.toolBarNewFolderBarButtonItem, flexibleItem, self.toolBarMoveBarButtonItem]]; break; } @@ -176,7 +177,7 @@ - (void)setupBrowser { case BrowserActionImportFromFolderLink: { [self setupDefaultElements]; - self.toolBarCopyBarButtonItem.title = NSLocalizedString(@"Import to Cloud Drive", @"Button title that triggers the importing link action"); + self.toolBarCopyBarButtonItem.title = LocalizedString(@"Import to Cloud Drive", @"Button title that triggers the importing link action"); [self setToolbarItems:@[self.toolBarNewFolderBarButtonItem, flexibleItem, self.toolBarCopyBarButtonItem]]; break; } @@ -184,7 +185,7 @@ - (void)setupBrowser { case BrowserActionOpenIn: { [self setupDefaultElements]; - self.toolBarSaveInMegaBarButtonItem.title = NSLocalizedString(@"upload", nil); + self.toolBarSaveInMegaBarButtonItem.title = LocalizedString(@"upload", @""); [self setToolbarItems:@[self.toolBarNewFolderBarButtonItem, flexibleItem, self.toolBarSaveInMegaBarButtonItem]]; break; } @@ -197,7 +198,7 @@ - (void)setupBrowser { if (self.browserAction == BrowserActionShareExtension) { self.navigationItem.rightBarButtonItem = nil; } - self.toolBarSaveInMegaBarButtonItem.title = self.browserAction == BrowserActionNewFileSave ? NSLocalizedString(@"save", nil) : NSLocalizedString(@"upload", nil); + self.toolBarSaveInMegaBarButtonItem.title = self.browserAction == BrowserActionNewFileSave ? LocalizedString(@"save", @"") : LocalizedString(@"upload", @""); [self setToolbarItems:@[self.toolBarNewFolderBarButtonItem, flexibleItem, self.toolBarSaveInMegaBarButtonItem]]; break; } @@ -205,7 +206,7 @@ - (void)setupBrowser { case BrowserActionSendFromCloudDrive: { [self setupDefaultElements]; - self.toolbarSendBarButtonItem.title = NSLocalizedString(@"send", @"Label for any 'Send' button, link, text, title, etc. - (String as short as possible)."); + self.toolbarSendBarButtonItem.title = LocalizedString(@"send", @"Label for any 'Send' button, link, text, title, etc. - (String as short as possible)."); [self setToolbarItems:@[flexibleItem, self.toolbarSendBarButtonItem]]; if (self.isParentBrowser) { @@ -228,7 +229,7 @@ - (void)setupBrowser { case BrowserActionSaveToCloudDrive: [self setupDefaultElements]; - self.toolBarSelectBarButtonItem.title = NSLocalizedString(@"cloudDrive.browser.saveToCloudDrive.title", @"Browser save to cloud drive select button title"); + self.toolBarSelectBarButtonItem.title = LocalizedString(@"cloudDrive.browser.saveToCloudDrive.title", @"Browser save to cloud drive select button title"); [self setToolbarItems:@[self.toolBarNewFolderBarButtonItem, flexibleItem, self.toolBarSelectBarButtonItem]]; break; @@ -236,7 +237,7 @@ - (void)setupBrowser { case BrowserActionSelectFolder: [self setupDefaultElements]; self.toolBarSelectBarButtonItem.enabled = self.isChildBrowser && self.parentNode.handle != MEGASdkManager.sharedMEGASdk.rootNode.handle; - self.toolBarSelectBarButtonItem.title = NSLocalizedString(@"Select Folder", nil); + self.toolBarSelectBarButtonItem.title = LocalizedString(@"Select Folder", @""); [self setToolbarItems:@[self.toolBarNewFolderBarButtonItem, flexibleItem, self.toolBarSelectBarButtonItem]]; break; @@ -250,19 +251,19 @@ - (void)setupDefaultElements { if (self.parentBrowser) { [self updateSelector]; - [self.incomingButton setTitle:NSLocalizedString(@"incoming", @"Title of the 'Incoming' Shared Items.") forState:UIControlStateNormal]; - [self.cloudDriveButton setTitle:NSLocalizedString(@"cloudDrive", @"Title of the Cloud Drive section") forState:UIControlStateNormal]; + [self.incomingButton setTitle:LocalizedString(@"incoming", @"Title of the 'Incoming' Shared Items.") forState:UIControlStateNormal]; + [self.cloudDriveButton setTitle:LocalizedString(@"cloudDrive", @"Title of the Cloud Drive section") forState:UIControlStateNormal]; } else { self.selectorView.hidden = YES; self.tableViewTopConstraint.constant = -self.selectorView.frame.size.height; } - self.cancelBarButtonItem = [UIBarButtonItem.alloc initWithTitle:NSLocalizedString(@"cancel", nil) + self.cancelBarButtonItem = [UIBarButtonItem.alloc initWithTitle:LocalizedString(@"cancel", @"") style:UIBarButtonItemStylePlain target:self action:@selector(cancel:)]; self.navigationItem.rightBarButtonItem = self.cancelBarButtonItem; - self.toolBarNewFolderBarButtonItem.title = NSLocalizedString(@"newFolder", @"Menu option from the `Add` section that allows you to create a 'New Folder'"); + self.toolBarNewFolderBarButtonItem.title = LocalizedString(@"newFolder", @"Menu option from the `Add` section that allows you to create a 'New Folder'"); } - (void)reloadUI { @@ -314,19 +315,13 @@ - (void)setParentNodeForBrowserAction { - (void)updatePromptTitle { if (self.browserAction == BrowserActionSendFromCloudDrive) { - NSString *promptString; - if (self.selectedNodesMutableDictionary.count == 0) { - promptString = NSLocalizedString(@"selectFiles", @"Text of the button for user to select files in MEGA."); - } else { - promptString = (self.selectedNodesMutableDictionary.count == 1) ? [NSString stringWithFormat:NSLocalizedString(@"oneItemSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected one photo"), self.selectedNodesMutableDictionary.count] : [NSString stringWithFormat:NSLocalizedString(@"itemsSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected more than one photo"), self.selectedNodesMutableDictionary.count]; - } - self.navigationItem.prompt = promptString; + self.navigationItem.prompt = [self promptForSelectedCount:self.selectedNodesMutableDictionary.count]; } else if (self.browserAction != BrowserActionDocumentProvider && self.browserAction != BrowserActionShareExtension && self.browserAction != BrowserActionSelectFolder && self.browserAction != BrowserActionNewHomeUpload && self.browserAction != BrowserActionNewFileSave) { - self.navigationItem.prompt = NSLocalizedString(@"selectDestination", @"Title shown on the navigation bar to explain that you have to choose a destination for the files and/or folders in case you copy, move, import or do some action with them."); + self.navigationItem.prompt = LocalizedString(@"selectDestination", @"Title shown on the navigation bar to explain that you have to choose a destination for the files and/or folders in case you copy, move, import or do some action with them."); } } @@ -361,25 +356,25 @@ - (NSString *)successMessageForCopyAction { NSString *message; if (files == 0) { if (folders == 1) { - message = NSLocalizedString(@"copyFolderMessage", nil); + message = LocalizedString(@"copyFolderMessage", @""); } else { //folders > 1 - message = [NSString stringWithFormat:NSLocalizedString(@"copyFoldersMessage", nil), folders]; + message = [NSString stringWithFormat:LocalizedString(@"copyFoldersMessage", @""), folders]; } } else if (files == 1) { if (folders == 0) { - message = NSLocalizedString(@"copyFileMessage", nil); + message = LocalizedString(@"copyFileMessage", @""); } else if (folders == 1) { - message = NSLocalizedString(@"copyFileFolderMessage", nil); + message = LocalizedString(@"copyFileFolderMessage", @""); } else { - message = [NSString stringWithFormat:NSLocalizedString(@"copyFileFoldersMessage", nil), folders]; + message = [NSString stringWithFormat:LocalizedString(@"copyFileFoldersMessage", @""), folders]; } } else { if (folders == 0) { - message = [NSString stringWithFormat:NSLocalizedString(@"copyFilesMessage", nil), files]; + message = [NSString stringWithFormat:LocalizedString(@"copyFilesMessage", @""), files]; } else if (folders == 1) { - message = [NSString stringWithFormat:NSLocalizedString(@"copyFilesFolderMessage", nil), files]; + message = [NSString stringWithFormat:LocalizedString(@"copyFilesFolderMessage", @""), files]; } else { - message = NSLocalizedString(@"copyFilesFoldersMessage", nil); + message = LocalizedString(@"copyFilesFoldersMessage", @""); NSString *filesString = [NSString stringWithFormat:@"%ld", (long)files]; NSString *foldersString = [NSString stringWithFormat:@"%ld", (long)folders]; message = [message stringByReplacingOccurrencesOfString:@"[A]" withString:filesString]; @@ -520,7 +515,7 @@ - (IBAction)copyNode:(UIBarButtonItem *)sender { } - (IBAction)newFolder:(UIBarButtonItem *)sender { - UIAlertController *newFolderAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"newFolder", @"Menu option from the `Add` section that allows you to create a 'New Folder'") message:nil preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *newFolderAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"newFolder", @"Menu option from the `Add` section that allows you to create a 'New Folder'") message:nil preferredStyle:UIAlertControllerStyleAlert]; __weak __typeof__(self) weakSelf = self; [newFolderAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { @@ -529,16 +524,16 @@ - (IBAction)newFolder:(UIBarButtonItem *)sender { return; } - textField.placeholder = NSLocalizedString(@"newFolderMessage", @"Hint text shown on the create folder alert."); + textField.placeholder = LocalizedString(@"newFolderMessage", @"Hint text shown on the create folder alert."); [textField addTarget:strongSelf action:@selector(newFolderAlertTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { return (!textField.text.mnz_isEmpty && !textField.text.mnz_containsInvalidChars); }; }]; - [newFolderAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [newFolderAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - UIAlertAction *createFolderAlertAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"createFolderButton", @"Title button for the create folder alert.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertAction *createFolderAlertAction = [UIAlertAction actionWithTitle:LocalizedString(@"createFolderButton", @"Title button for the create folder alert.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { __strong __typeof__(weakSelf) strongSelf = weakSelf; if (strongSelf == nil) { return; @@ -548,7 +543,7 @@ - (IBAction)newFolder:(UIBarButtonItem *)sender { UITextField *textField = [[newFolderAlertController textFields] firstObject]; MEGANodeList *childrenNodeList = [[MEGASdkManager sharedMEGASdk] nodeListSearchForNode:strongSelf.parentNode searchString:textField.text recursive:NO]; if ([childrenNodeList mnz_existsFolderWithName:textField.text]) { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"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.")]; + [SVProgressHUD showErrorWithStatus:LocalizedString(@"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 { MEGACreateFolderRequestDelegate *createFolderRequestDelegate = [[MEGACreateFolderRequestDelegate alloc] initWithCompletion:^(MEGARequest *request) { MEGANode *newFolderNode = [[MEGASdkManager sharedMEGASdk] nodeForHandle:request.nodeHandle]; @@ -873,17 +868,17 @@ - (NSString *)titleForEmptyState { if ([MEGAReachabilityManager isReachable]) { if (self.searchController.isActive) { if (self.searchController.searchBar.text.length > 0) { - text = NSLocalizedString(@"noResults", @"Title shown when you make a search and there is 'No Results'"); + text = LocalizedString(@"noResults", @"Title shown when you make a search and there is 'No Results'"); } } else { if (self.incomingButton.selected && self.isParentBrowser) { - text = NSLocalizedString(@"noIncomingSharedItemsEmptyState_text", @"Title shown when there's no incoming Shared Items"); + text = LocalizedString(@"noIncomingSharedItemsEmptyState_text", @"Title shown when there's no incoming Shared Items"); } else { - text = NSLocalizedString(@"emptyFolder", @"Title shown when a folder doesn't have any files"); + text = LocalizedString(@"emptyFolder", @"Title shown when a folder doesn't have any files"); } } } else { - text = NSLocalizedString(@"noInternetConnection", @"Text shown on the app when you don't have connection to the internet or when you have lost it"); + text = LocalizedString(@"noInternetConnection", @"Text shown on the app when you don't have connection to the internet or when you have lost it"); } return text; @@ -892,7 +887,7 @@ - (NSString *)titleForEmptyState { - (NSString *)descriptionForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); + text = LocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); } return text; @@ -924,7 +919,7 @@ - (UIImage *)imageForEmptyState { - (NSString *)buttonTitleForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Turn Mobile Data on", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); + text = LocalizedString(@"Turn Mobile Data on", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); } return text; @@ -961,7 +956,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [SVProgressHUD dismiss]; } else { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:NSLocalizedString(error.name, nil)]; + [SVProgressHUD showErrorWithStatus:LocalizedString(error.name, @"")]; } } return; @@ -979,9 +974,9 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [SVProgressHUD showSuccessWithStatus:message]; } else if (self.browserAction == BrowserActionImport || self.browserAction == BrowserActionImportFromFolderLink) { if ((self.selectedNodesArray.count == 1) && [self.selectedNodesArray.firstObject isFile]) { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"fileImported", @"Message shown when a file has been imported")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"fileImported", @"Message shown when a file has been imported")]; } else { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"filesImported", @"Message shown when some files have been imported")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"filesImported", @"Message shown when some files have been imported")]; } } diff --git a/iMEGA/Cloud drive/Cells/NodeCollectionViewCell+Nib.swift b/iMEGA/Cloud drive/Cells/NodeCollectionViewCell+Nib.swift index fc06c503e9..07132734e6 100644 --- a/iMEGA/Cloud drive/Cells/NodeCollectionViewCell+Nib.swift +++ b/iMEGA/Cloud drive/Cells/NodeCollectionViewCell+Nib.swift @@ -1,4 +1,3 @@ - extension NodeCollectionViewCell { private static let fileNibName: String = "FileNodeCollectionViewCell" diff --git a/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.h b/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.h index 02c11faea1..87bc719319 100644 --- a/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.h +++ b/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.m b/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.m index c213956305..1a6ce7e3e9 100644 --- a/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.m +++ b/iMEGA/Cloud drive/Cells/NodeCollectionViewCell.m @@ -1,4 +1,3 @@ - #import "NodeCollectionViewCell.h" #import "NSString+MNZCategory.h" diff --git a/iMEGA/Cloud drive/Cells/NodePropertyTableViewCell.h b/iMEGA/Cloud drive/Cells/NodePropertyTableViewCell.h index d57ee852ea..fe0b594f1a 100644 --- a/iMEGA/Cloud drive/Cells/NodePropertyTableViewCell.h +++ b/iMEGA/Cloud drive/Cells/NodePropertyTableViewCell.h @@ -1,4 +1,3 @@ - #import @interface NodePropertyTableViewCell : UITableViewCell diff --git a/iMEGA/Cloud drive/Cells/NodePropertyTableViewCell.m b/iMEGA/Cloud drive/Cells/NodePropertyTableViewCell.m index 5d3d5f1591..fd54fa2a8e 100644 --- a/iMEGA/Cloud drive/Cells/NodePropertyTableViewCell.m +++ b/iMEGA/Cloud drive/Cells/NodePropertyTableViewCell.m @@ -1,4 +1,3 @@ - #import "NodePropertyTableViewCell.h" @implementation NodePropertyTableViewCell diff --git a/iMEGA/Cloud drive/Cells/NodeTableViewCell+Additions.swift b/iMEGA/Cloud drive/Cells/NodeTableViewCell+Additions.swift index 9fbc3e0f91..1bb24cbc55 100644 --- a/iMEGA/Cloud drive/Cells/NodeTableViewCell+Additions.swift +++ b/iMEGA/Cloud drive/Cells/NodeTableViewCell+Additions.swift @@ -1,3 +1,4 @@ +import MEGAL10n extension NodeTableViewCell { @@ -11,7 +12,7 @@ extension NodeTableViewCell { } let isNodeUndecrypted = firstNode.isUndecrypted(ownerEmail: recentActionBucket.userEmail, - in: MEGASdkManager.sharedMEGASdk()) + in: .shared) guard !isNodeUndecrypted else { infoLabel.text = Strings.Localizable.SharedItems.Tab.Incoming.undecryptedFolderName nameLabel.text = Strings.Localizable.SharedItems.Tab.Recents.undecryptedFileName(nodes.count) @@ -22,7 +23,7 @@ extension NodeTableViewCell { let nodesCount = nodes.count nameLabel.text = nodesCount == 1 ? firstNodeName : Strings.Localizable.Recents.Section.MultipleFile.title(nodesCount - 1).replacingOccurrences(of: "[A]", with: firstNodeName) - let parentNode = MEGASdkManager.sharedMEGASdk().node(forHandle: recentActionBucket.parentHandle) + let parentNode = MEGASdk.shared.node(forHandle: recentActionBucket.parentHandle) let parentNodeName = parentNode?.name ?? "" infoLabel.text = "\(parentNodeName) ・" } diff --git a/iMEGA/Cloud drive/Cells/NodeTableViewCell.h b/iMEGA/Cloud drive/Cells/NodeTableViewCell.h index 29d1a1f7d1..c8a58def78 100644 --- a/iMEGA/Cloud drive/Cells/NodeTableViewCell.h +++ b/iMEGA/Cloud drive/Cells/NodeTableViewCell.h @@ -1,4 +1,3 @@ - /* NodeTableViewCellFlavor is used to tell how `NodeTableViewCell` being used, e.g. CloudDriveCell, RecentCell, SharedLinkCell. In fact, this is a work around to tell the different scenario the same cell is reused, which should be prohibited, as it's diff --git a/iMEGA/Cloud drive/Cells/NodeTableViewCell.m b/iMEGA/Cloud drive/Cells/NodeTableViewCell.m index 01525941e4..4d6b8c3938 100644 --- a/iMEGA/Cloud drive/Cells/NodeTableViewCell.m +++ b/iMEGA/Cloud drive/Cells/NodeTableViewCell.m @@ -18,6 +18,7 @@ #endif @import MEGASDKRepo; +@import MEGAL10nObjc; @interface NodeTableViewCell() @@ -229,7 +230,7 @@ - (void)updateInfo { self.infoStringRightLabel.lineBreakMode = NSLineBreakByTruncatingHead; BOOL shouldIncludeRootFolder = self.node.isInShare || (self.node.parentHandle == MEGASdkManager.sharedMEGASdk.rootNode.handle); - self.infoLabel.text = shouldIncludeRootFolder ? NSLocalizedString(@"", nil) : NSLocalizedString(@"> ", nil); + self.infoLabel.text = shouldIncludeRootFolder ? LocalizedString(@"", @"") : LocalizedString(@"> ", @""); self.infoStringRightLabel.text = [self.node filePathWithDelimeter:@" > " sdk:MEGASdkManager.sharedMEGASdk includeRootFolderName:shouldIncludeRootFolder diff --git a/iMEGA/Cloud drive/Cells/NodeTappablePropertyTableViewCell.h b/iMEGA/Cloud drive/Cells/NodeTappablePropertyTableViewCell.h index 951399c300..53dc7f8e2d 100644 --- a/iMEGA/Cloud drive/Cells/NodeTappablePropertyTableViewCell.h +++ b/iMEGA/Cloud drive/Cells/NodeTappablePropertyTableViewCell.h @@ -1,4 +1,3 @@ - #import @interface NodeTappablePropertyTableViewCell : UITableViewCell diff --git a/iMEGA/Cloud drive/Cells/NodeTappablePropertyTableViewCell.m b/iMEGA/Cloud drive/Cells/NodeTappablePropertyTableViewCell.m index 9b99d2ab70..b54e7ce2ae 100644 --- a/iMEGA/Cloud drive/Cells/NodeTappablePropertyTableViewCell.m +++ b/iMEGA/Cloud drive/Cells/NodeTappablePropertyTableViewCell.m @@ -1,4 +1,3 @@ - #import "NodeTappablePropertyTableViewCell.h" @implementation NodeTappablePropertyTableViewCell diff --git a/iMEGA/Cloud drive/Cells/ThumbnailViewerTableViewCell.h b/iMEGA/Cloud drive/Cells/ThumbnailViewerTableViewCell.h index 140f505c00..4f8759f4b3 100644 --- a/iMEGA/Cloud drive/Cells/ThumbnailViewerTableViewCell.h +++ b/iMEGA/Cloud drive/Cells/ThumbnailViewerTableViewCell.h @@ -1,4 +1,3 @@ - #import @protocol NodeDisplayDelegate diff --git a/iMEGA/Cloud drive/Cells/ThumbnailViewerTableViewCell.m b/iMEGA/Cloud drive/Cells/ThumbnailViewerTableViewCell.m index 1c4c2326c1..e1d6b24575 100644 --- a/iMEGA/Cloud drive/Cells/ThumbnailViewerTableViewCell.m +++ b/iMEGA/Cloud drive/Cells/ThumbnailViewerTableViewCell.m @@ -1,4 +1,3 @@ - #import "ThumbnailViewerTableViewCell.h" #import "ItemCollectionViewCell.h" @@ -16,6 +15,8 @@ #import "UIImageView+MNZCategory.h" #import "MEGA-Swift.h" #import "MEGARecentActionBucket+MNZCategory.h" + +@import MEGAL10nObjc; @import MEGASDKRepo; @interface ThumbnailViewerTableViewCell () @@ -74,16 +75,16 @@ - (void)configureForRecentAction:(MEGARecentActionBucket *)recentActionBucket { NSString *title; if (numberOfPhotos == 0) { - NSString *videoCountFormat = NSLocalizedString(@"recents.section.thumbnail.count.video", @"Multiple videos title shown in recents section of web client."); + NSString *videoCountFormat = LocalizedString(@"recents.section.thumbnail.count.video", @"Multiple videos title shown in recents section of web client."); title = [NSString stringWithFormat:videoCountFormat, numberOfVideos]; } else if (numberOfVideos == 0) { - NSString *imageCountFormat = NSLocalizedString(@"recents.section.thumbnail.count.image", @"Multiple Images title shown in recents section of webclient"); + NSString *imageCountFormat = LocalizedString(@"recents.section.thumbnail.count.image", @"Multiple Images title shown in recents section of webclient"); title = [NSString stringWithFormat:imageCountFormat, numberOfPhotos]; } else { - NSString *imageCountFormat = NSLocalizedString(@"recents.section.thumbnail.count.imageAndVideo.image", @"Image count on recents section that will be concatenated with number of videos. e.g 1 image and 1 video, 2 images and 1 video"); + NSString *imageCountFormat = LocalizedString(@"recents.section.thumbnail.count.imageAndVideo.image", @"Image count on recents section that will be concatenated with number of videos. e.g 1 image and 1 video, 2 images and 1 video"); NSString *imageCount = [NSString stringWithFormat:imageCountFormat, numberOfPhotos]; - NSString *videoCountFormat = NSLocalizedString(@"recents.section.thumbnail.count.imageAndVideo.video", @"Video count on recents section that will be concatenated with number of images. e.g 1 image and 1 video, 2 images and 1 video"); + NSString *videoCountFormat = LocalizedString(@"recents.section.thumbnail.count.imageAndVideo.video", @"Video count on recents section that will be concatenated with number of images. e.g 1 image and 1 video, 2 images and 1 video"); NSString *videoCount = [NSString stringWithFormat:videoCountFormat, numberOfVideos]; title = [NSString stringWithFormat:@"%@ %@", imageCount, videoCount]; diff --git a/iMEGA/Cloud drive/CloudDriveCollectionViewController.h b/iMEGA/Cloud drive/CloudDriveCollectionViewController.h index 6ba07a6094..a35678b1d6 100644 --- a/iMEGA/Cloud drive/CloudDriveCollectionViewController.h +++ b/iMEGA/Cloud drive/CloudDriveCollectionViewController.h @@ -1,4 +1,3 @@ - #import #import "NodeCollectionViewCell.h" diff --git a/iMEGA/Cloud drive/CloudDriveCollectionViewController.m b/iMEGA/Cloud drive/CloudDriveCollectionViewController.m index 9584ea3ce4..6a9214aff3 100644 --- a/iMEGA/Cloud drive/CloudDriveCollectionViewController.m +++ b/iMEGA/Cloud drive/CloudDriveCollectionViewController.m @@ -1,4 +1,3 @@ - #import "CloudDriveCollectionViewController.h" #import "NSString+MNZCategory.h" @@ -13,6 +12,7 @@ #import "MEGA-Swift.h" #import "NSArray+MNZCategory.h" +@import MEGAL10nObjc; @import MEGAUIKit; @interface CloudDriveCollectionViewController () @@ -182,7 +182,7 @@ - (UIContextMenuConfiguration *)collectionView:(UICollectionView *)collectionVie return nil; } } actionProvider:^UIMenu * _Nullable(NSArray * _Nonnull suggestedActions) { - UIAction *selectAction = [UIAction actionWithTitle:NSLocalizedString(@"select", nil) + UIAction *selectAction = [UIAction actionWithTitle:LocalizedString(@"select", @"") image:[UIImage imageNamed:@"select"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { diff --git a/iMEGA/Cloud drive/CloudDriveTableViewController+Additions.swift b/iMEGA/Cloud drive/CloudDriveTableViewController+Additions.swift index f47c1a812a..3bcdf1f77a 100644 --- a/iMEGA/Cloud drive/CloudDriveTableViewController+Additions.swift +++ b/iMEGA/Cloud drive/CloudDriveTableViewController+Additions.swift @@ -1,13 +1,12 @@ - extension CloudDriveTableViewController { @objc func configureSwipeActionsForIndex(_ index: IndexPath) -> UISwipeActionsConfiguration { - guard let node = self.cloudDrive?.node(at: index), MEGASdkManager.sharedMEGASdk().accessLevel(for: node) == .accessOwner else { + guard let node = self.cloudDrive?.node(at: index), MEGASdk.shared.accessLevel(for: node) == .accessOwner else { return UISwipeActionsConfiguration(actions: []) } - if MEGASdkManager.sharedMEGASdk().isNode(inRubbish: node) { - if let restoreNode = MEGASdkManager.sharedMEGASdk().node(forHandle: node.restoreHandle), - !MEGASdkManager.sharedMEGASdk().isNode(inRubbish: restoreNode) { + if MEGASdk.shared.isNode(inRubbish: node) { + if let restoreNode = MEGASdk.shared.node(forHandle: node.restoreHandle), + !MEGASdk.shared.isNode(inRubbish: restoreNode) { let restoreAction = swipeAction(image: Asset.Images.NodeActions.restore.image.withTintColor(.white), backgroundColor: UIColor.mnz_turquoise(for: traitCollection)) { [weak self] in node.mnz_restore() self?.setTableViewEditing(false, animated: true) diff --git a/iMEGA/Cloud drive/CloudDriveTableViewController.h b/iMEGA/Cloud drive/CloudDriveTableViewController.h index 0eb381f6d1..cad43b7fdd 100644 --- a/iMEGA/Cloud drive/CloudDriveTableViewController.h +++ b/iMEGA/Cloud drive/CloudDriveTableViewController.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Cloud drive/CloudDriveTableViewController.m b/iMEGA/Cloud drive/CloudDriveTableViewController.m index c94a6de4f0..74bdc58b2e 100644 --- a/iMEGA/Cloud drive/CloudDriveTableViewController.m +++ b/iMEGA/Cloud drive/CloudDriveTableViewController.m @@ -1,4 +1,3 @@ - #import "CloudDriveTableViewController.h" #import "UIImageView+MNZCategory.h" @@ -15,6 +14,7 @@ #import "NodeTableViewCell.h" @import MEGAFoundation; +@import MEGAL10nObjc; @interface CloudDriveTableViewController () @end @@ -73,9 +73,9 @@ - (UIView *)prepareBucketHeaderView { NSString *dateString; if (self.cloudDrive.recentActionBucket.timestamp.isToday) { - dateString = NSLocalizedString(@"Today", @""); + dateString = LocalizedString(@"Today", @""); } else if (self.cloudDrive.recentActionBucket.timestamp.isYesterday) { - dateString = NSLocalizedString(@"Yesterday", @""); + dateString = LocalizedString(@"Yesterday", @""); } else { dateString = self.cloudDrive.recentActionBucket.timestamp.mnz_formattedDateMediumStyle; } @@ -288,7 +288,7 @@ - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView return nil; } } actionProvider:^UIMenu * _Nullable(NSArray * _Nonnull suggestedActions) { - UIAction *selectAction = [UIAction actionWithTitle:NSLocalizedString(@"select", nil) + UIAction *selectAction = [UIAction actionWithTitle:LocalizedString(@"select", @"") image:[UIImage imageNamed:@"select"] identifier:nil handler:^(__kindof UIAction * _Nonnull action) { diff --git a/iMEGA/Cloud drive/CloudDriveViewController+Additions.swift b/iMEGA/Cloud drive/CloudDriveViewController+Additions.swift index 83ddb9604c..e648e6334e 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController+Additions.swift +++ b/iMEGA/Cloud drive/CloudDriveViewController+Additions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo import SwiftUI @@ -148,7 +149,7 @@ extension CloudDriveViewController { if let selectedNodesArray { selectedNodesArrayCount = selectedNodesArray.count } if let enableEditing = cdTableView?.tableView?.isEditing ?? cdCollectionView?.collectionView?.allowsMultipleSelection, enableEditing { - navigationTitle = selectedNodesArrayCount == 0 ? Strings.Localizable.selectTitle : selectedNodesArrayCount == 1 ? Strings.Localizable.oneItemSelected(1) : Strings.Localizable.itemsSelected(selectedNodesArrayCount) + navigationTitle = selectedNodesArrayCount == 0 ? Strings.Localizable.selectTitle : Strings.Localizable.General.Format.itemsSelected(selectedNodesArrayCount) } else { switch displayMode { case .cloudDrive: diff --git a/iMEGA/Cloud drive/CloudDriveViewController+Backup.swift b/iMEGA/Cloud drive/CloudDriveViewController+Backup.swift index d96d0784cc..bf3f00965d 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController+Backup.swift +++ b/iMEGA/Cloud drive/CloudDriveViewController+Backup.swift @@ -1,11 +1,12 @@ import MEGADomain +import MEGAL10n extension CloudDriveViewController { private func contextMenuBackupConfiguration() -> CMConfigEntity? { guard let parentNode else { return nil } - let parentNodeAccessLevel = MEGASdkManager.sharedMEGASdk().accessLevel(for: parentNode) - let isIncomingSharedRootChild = parentNodeAccessLevel != .accessOwner && MEGASdkManager.sharedMEGASdk().parentNode(for: parentNode) == nil + let parentNodeAccessLevel = MEGASdk.shared.accessLevel(for: parentNode) + let isIncomingSharedRootChild = parentNodeAccessLevel != .accessOwner && MEGASdk.shared.parentNode(for: parentNode) == nil let parentNodeEntity = parentNode.toNodeEntity() let backupsUseCase = BackupsUseCase(backupsRepository: BackupsRepository.newRepo, nodeRepository: NodeRepository.newRepo) let isBackupsNode = backupsUseCase.isBackupsRootNode(parentNodeEntity) @@ -38,7 +39,7 @@ extension CloudDriveViewController { displayMode != .backup, !isFromViewInFolder, let parentNode = parentNode, - MEGASdkManager.sharedMEGASdk().accessLevel(for: parentNode) != .accessRead { + MEGASdk.shared.accessLevel(for: parentNode) != .accessRead { guard let menuConfig = uploadAddMenuConfiguration() else { return } uploadAddBarButtonItem = UIBarButtonItem(image: Asset.Images.NavigationBar.add.image, menu: contextMenuManager?.contextMenu(with: menuConfig)) @@ -66,7 +67,7 @@ extension CloudDriveViewController { let backupsUseCase = BackupsUseCase(backupsRepository: BackupsRepository.newRepo, nodeRepository: NodeRepository.newRepo) let isBackupNode = backupsUseCase.isBackupNode(node.toNodeEntity()) - let shareType: MEGAShareType = isBackupNode ? .accessRead : MEGASdkManager.sharedMEGASdk().accessLevel(for: parentNode) + let shareType: MEGAShareType = isBackupNode ? .accessRead : MEGASdk.shared.accessLevel(for: parentNode) toolbarActions(for: shareType, isBackupNode: isBackupNode) } diff --git a/iMEGA/Cloud drive/CloudDriveViewController+CameraUpload.swift b/iMEGA/Cloud drive/CloudDriveViewController+CameraUpload.swift index 45cc364d21..298931d854 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController+CameraUpload.swift +++ b/iMEGA/Cloud drive/CloudDriveViewController+CameraUpload.swift @@ -1,3 +1,4 @@ +import MEGAL10n extension CloudDriveViewController { @IBAction func deleteAction(sender: UIBarButtonItem) { @@ -26,7 +27,7 @@ extension CloudDriveViewController { } @objc func moveToRubbishBin(for node: MEGANode) { - guard let rubbish = MEGASdkManager.sharedMEGASdk().rubbishNode else { + guard let rubbish = MEGASdk.shared.rubbishNode else { self.dismiss(animated: true) return } @@ -40,12 +41,12 @@ extension CloudDriveViewController { return } - if cuNode.isDescendant(of: node, in: MEGASdkManager.sharedMEGASdk()) { + if cuNode.isDescendant(of: node, in: .shared) { self.promptCameraUploadFolderDeletion { let delegate = MEGAMoveRequestDelegate(toMoveToTheRubbishBinWithFiles: 0, folders: 1) { self.dismiss(animated: true) } - MEGASdkManager.sharedMEGASdk().move(node, newParent: rubbish, delegate: delegate) + MEGASdk.shared.move(node, newParent: rubbish, delegate: delegate) } } else { node.mnz_askToMoveToTheRubbishBin(in: self) @@ -56,7 +57,7 @@ extension CloudDriveViewController { private func deleteSelectedNodes() { guard let selectedNodes = selectedNodesArray as? [MEGANode], - let rubbish = MEGASdkManager.sharedMEGASdk().rubbishNode else { + let rubbish = MEGASdk.shared.rubbishNode else { return } @@ -66,7 +67,7 @@ extension CloudDriveViewController { } for node in selectedNodes { - MEGASdkManager.sharedMEGASdk().move(node, newParent: rubbish, delegate: delegate) + MEGASdk.shared.move(node, newParent: rubbish, delegate: delegate) } } @@ -81,7 +82,7 @@ extension CloudDriveViewController { guard let cuNode = node else { return } let isSelected = selectedNodes.contains { - cuNode.isDescendant(of: $0, in: MEGASdkManager.sharedMEGASdk()) + cuNode.isDescendant(of: $0, in: .shared) } completion(isSelected) diff --git a/iMEGA/Cloud drive/CloudDriveViewController+ContextMenu.swift b/iMEGA/Cloud drive/CloudDriveViewController+ContextMenu.swift index 88d4e8b85b..b607d30844 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController+ContextMenu.swift +++ b/iMEGA/Cloud drive/CloudDriveViewController+ContextMenu.swift @@ -1,5 +1,6 @@ import CoreServices import MEGADomain +import MEGAL10n import MEGAPermissions import MEGASDKRepo import UIKit @@ -9,14 +10,14 @@ extension CloudDriveViewController: CloudDriveContextMenuDelegate { func contextMenuConfiguration(parentNode: MEGANode, parentAccessLevel: NodeAccessTypeEntity) -> CMConfigEntity { if parentNode.isFolder(), displayMode == .rubbishBin, - parentNode.handle != MEGASdkManager.sharedMEGASdk().rubbishNode?.handle { + parentNode.handle != MEGASdk.shared.rubbishNode?.handle { return CMConfigEntity(menuType: .menu(type: .rubbishBin), viewMode: isListViewModeSelected() ? .list : .thumbnail, sortType: SortOrderType(megaSortOrderType: Helper.sortType(for: parentNode)).megaSortOrderType.toSortOrderEntity(), isRubbishBinFolder: true, isRestorable: parentNode.mnz_isRestorable()) } else { - let isIncomingSharedRootChild = parentAccessLevel != .owner && MEGASdkManager.sharedMEGASdk().parentNode(for: parentNode) == nil + let isIncomingSharedRootChild = parentAccessLevel != .owner && MEGASdk.shared.parentNode(for: parentNode) == nil return CMConfigEntity(menuType: .menu(type: .display), viewMode: isListViewModeSelected() ? .list : .thumbnail, accessLevel: parentAccessLevel.toShareAccessLevel(), @@ -111,7 +112,7 @@ extension CloudDriveViewController: CloudDriveContextMenuDelegate { alertController.addAction(UIAlertAction(title: Strings.Localizable.cancel, style: .cancel)) alertController.addAction(UIAlertAction(title: Strings.Localizable.ok, style: .default) { _ in - MEGASdkManager.sharedMEGASdk().cleanRubbishBin() + MEGASdk.shared.cleanRubbishBin() }) UIApplication.mnz_visibleViewController().present(alertController, animated: true, completion: nil) diff --git a/iMEGA/Cloud drive/CloudDriveViewController+EmptyState.swift b/iMEGA/Cloud drive/CloudDriveViewController+EmptyState.swift index 87d5835fd2..6f0cf16d99 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController+EmptyState.swift +++ b/iMEGA/Cloud drive/CloudDriveViewController+EmptyState.swift @@ -1,3 +1,4 @@ +import MEGAL10n extension CloudDriveViewController: DZNEmptyDataSetSource { public func customView(forEmptyDataSet scrollView: UIScrollView) -> UIView? { @@ -78,7 +79,7 @@ extension CloudDriveViewController: DZNEmptyDataSetSource { return nil } - let parentShareType = MEGASdkManager.sharedMEGASdk().accessLevel(for: parentNode) + let parentShareType = MEGASdk.shared.accessLevel(for: parentNode) if parentShareType == .accessRead { return nil diff --git a/iMEGA/Cloud drive/CloudDriveViewController+MedisDiscovery.swift b/iMEGA/Cloud drive/CloudDriveViewController+MedisDiscovery.swift index 28c7b2f45b..3346aefd03 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController+MedisDiscovery.swift +++ b/iMEGA/Cloud drive/CloudDriveViewController+MedisDiscovery.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n extension CloudDriveViewController { @objc func shouldShowMediaDiscovery() -> Bool { diff --git a/iMEGA/Cloud drive/CloudDriveViewController+MyAvatar.swift b/iMEGA/Cloud drive/CloudDriveViewController+MyAvatar.swift index b64fbd186f..eb1944f6c6 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController+MyAvatar.swift +++ b/iMEGA/Cloud drive/CloudDriveViewController+MyAvatar.swift @@ -1,4 +1,3 @@ - extension CloudDriveViewController: MyAvatarPresenterProtocol { func setupMyAvatar(barButton: UIBarButtonItem) { self.navigationItem.leftBarButtonItem = barButton diff --git a/iMEGA/Cloud drive/CloudDriveViewController+NodeActionViewControllerDelegate.swift b/iMEGA/Cloud drive/CloudDriveViewController+NodeActionViewControllerDelegate.swift index 6111793edb..c1e1e66365 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController+NodeActionViewControllerDelegate.swift +++ b/iMEGA/Cloud drive/CloudDriveViewController+NodeActionViewControllerDelegate.swift @@ -56,12 +56,7 @@ extension CloudDriveViewController: NodeActionViewControllerDelegate { showNodeInfo(node) case .favourite: wasSelectingFavoriteUnfavoriteNodeActionOption = true - MEGASdkManager.sharedMEGASdk().setNodeFavourite(node, favourite: !node.isFavourite, delegate: MEGAGenericRequestDelegate(completion: { (request, error) in - if error.type == .apiOk { - request.numDetails == 1 ? QuickAccessWidgetManager().insertFavouriteItem(for: node) : - QuickAccessWidgetManager().deleteFavouriteItem(for: node) - } - })) + MEGASdk.shared.setNodeFavourite(node, favourite: !node.isFavourite) case .label: node.mnz_labelActionSheet(in: self) case .leaveSharing: @@ -136,7 +131,7 @@ extension CloudDriveViewController: NodeActionViewControllerDelegate { } private func saveToPhotos(nodes: [NodeEntity]) { - let saveMediaUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: MEGASdkManager.sharedMEGASdk()), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) + let saveMediaUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: MEGASdk.shared), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) Task { @MainActor in do { try await saveMediaUseCase.saveToPhotos(nodes: nodes) diff --git a/iMEGA/Cloud drive/CloudDriveViewController.m b/iMEGA/Cloud drive/CloudDriveViewController.m index a24879f419..5a7d6c9999 100644 --- a/iMEGA/Cloud drive/CloudDriveViewController.m +++ b/iMEGA/Cloud drive/CloudDriveViewController.m @@ -1,4 +1,3 @@ - #import "CloudDriveViewController.h" #import @@ -48,6 +47,7 @@ #import "UIViewController+MNZCategory.h" @import Photos; +@import MEGAL10nObjc; @import MEGAUIKit; @import MEGASDKRepo; @@ -142,7 +142,7 @@ - (void)viewDidLoad { self.nodesIndexPathMutableDictionary = [[NSMutableDictionary alloc] init]; - self.moreBarButtonItem.accessibilityLabel = NSLocalizedString(@"more", @"Top menu option which opens more menu options in a context menu."); + self.moreBarButtonItem.accessibilityLabel = LocalizedString(@"more", @"Top menu option which opens more menu options in a context menu."); _searchQueue = NSOperationQueue.new; self.searchQueue.name = @"searchQueue"; @@ -340,16 +340,16 @@ - (void)initCollection { - (void)didSelectNode:(MEGANode *)node { if (node.isTakenDown) { - NSString *alertMessage = node.isFolder ? NSLocalizedString(@"This folder has been the subject of a takedown notice.", @"Popup notification text on mouse-over taken down folder.") : NSLocalizedString(@"This file has been the subject of a takedown notice.", @"Popup notification text on mouse-over of taken down file."); + NSString *alertMessage = node.isFolder ? LocalizedString(@"This folder has been the subject of a takedown notice.", @"Popup notification text on mouse-over taken down folder.") : LocalizedString(@"This file has been the subject of a takedown notice.", @"Popup notification text on mouse-over of taken down file."); UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"openButton", @"Button title to trigger the action of opening the file without downloading or opening it.") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"openButton", @"Button title to trigger the action of opening the file without downloading or opening it.") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { node.isFolder ? [self openFolderNode:node] : [self openFileNode:node]; }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Dispute Takedown", @"File Manager -> Context menu item for taken down file or folder, for dispute takedown.") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"Dispute Takedown", @"File Manager -> Context menu item for taken down file or folder, for dispute takedown.") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [[NSURL URLWithString:MEGADisputeURL] mnz_presentSafariViewController]; }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; } else { @@ -476,7 +476,7 @@ - (void)prepareNodesAndNavigationBarForDisplayMode:(DisplayMode)displayMode { } - (void)loadPhotoAlbumBrowser { - AlbumsTableViewController *albumTableViewController = [AlbumsTableViewController.alloc initWithSelectionActionType:AlbumsSelectionActionTypeUpload selectionActionDisabledText:NSLocalizedString(@"upload", @"Used in Photos app browser view as a disabled action when there is no assets selected") completionBlock:^(NSArray * _Nonnull assets) { + AlbumsTableViewController *albumTableViewController = [AlbumsTableViewController.alloc initWithSelectionActionType:AlbumsSelectionActionTypeUpload selectionActionDisabledText:LocalizedString(@"upload", @"Used in Photos app browser view as a disabled action when there is no assets selected") completionBlock:^(NSArray * _Nonnull assets) { if (assets.count > 0) { for (PHAsset *asset in assets) { [MEGAStore.shareInstance insertUploadTransferWithLocalIdentifier:asset.localIdentifier parentNodeHandle:self.parentNode.handle]; @@ -546,7 +546,7 @@ - (void)setNavigationBarButtonItems { case DisplayModeRecents: self.navigationItem.rightBarButtonItems = @[]; - self.navigationItem.leftBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"close", @"A button label.") + self.navigationItem.leftBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:LocalizedString(@"close", @"A button label.") style:UIBarButtonItemStylePlain target:self action:@selector(dismissSelf)]; @@ -601,7 +601,7 @@ - (void)newFolderAlertTextFieldDidChange:(UITextField *)textField { - (void)presentScanDocument { if (!VNDocumentCameraViewController.isSupported) { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"Document scanning is not available", @"A tooltip message which is shown when device does not support document scanning")]; + [SVProgressHUD showErrorWithStatus:LocalizedString(@"Document scanning is not available", @"A tooltip message which is shown when device does not support document scanning")]; return; } @@ -743,7 +743,9 @@ - (void)search { - (void)cancelSearchIfNeeded { if (self.searchQueue.operationCount) { - [self.cancelToken cancel]; + @synchronized(self) { + [self.cancelToken cancel]; + } [self.searchQueue cancelAllOperations]; } } @@ -773,9 +775,9 @@ - (void)confirmDeleteActionFiles:(NSUInteger)numFilesAction andFolders:(NSUInteg NSString *alertTitle = [self.viewModel alertTitleForRemovedFiles:numFilesAction andFolders:numFoldersAction]; NSString *message = [self.viewModel alertMessageForRemovedFiles:numFilesAction andFolders:numFoldersAction]; UIAlertController *removeAlertController = [UIAlertController alertControllerWithTitle:alertTitle message:message preferredStyle:UIAlertControllerStyleAlert]; - [removeAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [removeAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - [removeAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to cancel something") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [removeAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to cancel something") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { MEGARemoveRequestDelegate *removeRequestDelegate = [MEGARemoveRequestDelegate.alloc initWithMode:DisplayModeRubbishBin files:numFilesAction folders:numFoldersAction completion:^{ [self setEditMode:NO]; }]; @@ -820,24 +822,24 @@ - (IBAction)selectAllAction:(UIBarButtonItem *)sender { - (void)createNewFolderAction { __weak __typeof__(self) weakSelf = self; - UIAlertController *newFolderAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"newFolder", @"Menu option from the `Add` section that allows you to create a 'New Folder'") message:nil preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *newFolderAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"newFolder", @"Menu option from the `Add` section that allows you to create a 'New Folder'") message:nil preferredStyle:UIAlertControllerStyleAlert]; [newFolderAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.placeholder = NSLocalizedString(@"newFolderMessage", @"Hint text shown on the create folder alert."); + textField.placeholder = LocalizedString(@"newFolderMessage", @"Hint text shown on the create folder alert."); [textField addTarget:weakSelf action:@selector(newFolderAlertTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { return (!textField.text.mnz_isEmpty && !textField.text.mnz_containsInvalidChars); }; }]; - [newFolderAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [newFolderAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - UIAlertAction *createFolderAlertAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"createFolderButton", @"Title button for the create folder alert.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertAction *createFolderAlertAction = [UIAlertAction actionWithTitle:LocalizedString(@"createFolderButton", @"Title button for the create folder alert.") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { if ([MEGAReachabilityManager isReachableHUDIfNot]) { UITextField *textField = [[newFolderAlertController textFields] firstObject]; MEGANode *existingChildNode = [[MEGASdkManager sharedMEGASdk] childNodeForParent:weakSelf.parentNode name:textField.text type:MEGANodeTypeFolder]; if (existingChildNode) { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"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.")]; + [SVProgressHUD showErrorWithStatus:LocalizedString(@"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 { MEGACreateFolderRequestDelegate *createFolderRequestDelegate = [[MEGACreateFolderRequestDelegate alloc] initWithCompletion:^(MEGARequest *request) { MEGANode *newFolderNode = [[MEGASdkManager sharedMEGASdk] nodeForHandle:request.nodeHandle]; @@ -862,7 +864,7 @@ - (void)setViewEditing:(BOOL)editing { [self updateNavigationBarTitle]; if (editing) { - self.editBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.editBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); self.navigationItem.rightBarButtonItems = @[self.editBarButtonItem]; self.navigationItem.leftBarButtonItems = @[self.selectAllBarButtonItem]; @@ -1043,7 +1045,7 @@ - (void)documentCameraViewController:(VNDocumentCameraViewController *)controlle vc.docs = docs.copy; [self presentViewController:({ MEGANavigationController *nav = [MEGANavigationController.alloc initWithRootViewController:vc]; - [nav addLeftDismissButtonWithText:NSLocalizedString(@"cancel", nil)]; + [nav addLeftDismissButtonWithText:LocalizedString(@"cancel", @"")]; nav.modalPresentationStyle = UIModalPresentationFullScreen; nav; }) animated:YES completion:nil]; diff --git a/iMEGA/Cloud drive/CloudDriveViewModel.swift b/iMEGA/Cloud drive/CloudDriveViewModel.swift index 87338f8f0b..5481aa232d 100644 --- a/iMEGA/Cloud drive/CloudDriveViewModel.swift +++ b/iMEGA/Cloud drive/CloudDriveViewModel.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n @objc final class CloudDriveViewModel: NSObject { private let router = SharedItemsViewRouter() diff --git a/iMEGA/Cloud drive/CopyrightWarning/EnforceCopyrightWarningView.swift b/iMEGA/Cloud drive/CopyrightWarning/EnforceCopyrightWarningView.swift index ae288f8eb1..c9f2238e09 100644 --- a/iMEGA/Cloud drive/CopyrightWarning/EnforceCopyrightWarningView.swift +++ b/iMEGA/Cloud drive/CopyrightWarning/EnforceCopyrightWarningView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGASwiftUI import SwiftUI diff --git a/iMEGA/Cloud drive/CopyrightWarning/EnforceCopyrightWarningViewModel.swift b/iMEGA/Cloud drive/CopyrightWarning/EnforceCopyrightWarningViewModel.swift index 70b8cb601a..719800ae40 100644 --- a/iMEGA/Cloud drive/CopyrightWarning/EnforceCopyrightWarningViewModel.swift +++ b/iMEGA/Cloud drive/CopyrightWarning/EnforceCopyrightWarningViewModel.swift @@ -1,6 +1,7 @@ import Combine import Foundation import MEGADomain +import MEGAL10n final class EnforceCopyrightWarningViewModel: ObservableObject { enum CopyrightWarningViewStatus { diff --git a/iMEGA/Cloud drive/DocScannerActionTableViewCell.swift b/iMEGA/Cloud drive/DocScannerActionTableViewCell.swift index b78394a7f9..c5d6ab4494 100644 --- a/iMEGA/Cloud drive/DocScannerActionTableViewCell.swift +++ b/iMEGA/Cloud drive/DocScannerActionTableViewCell.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit class DocScannerActionTableViewCell: UITableViewCell { diff --git a/iMEGA/Cloud drive/DocScannerDetailTableCell.swift b/iMEGA/Cloud drive/DocScannerDetailTableCell.swift index 040f8a620e..44957518ed 100644 --- a/iMEGA/Cloud drive/DocScannerDetailTableCell.swift +++ b/iMEGA/Cloud drive/DocScannerDetailTableCell.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit class DocScannerDetailTableCell: UITableViewCell { diff --git a/iMEGA/Cloud drive/DocScannerFileNameTableCell.swift b/iMEGA/Cloud drive/DocScannerFileNameTableCell.swift index f3f5947e9a..4fb837224c 100644 --- a/iMEGA/Cloud drive/DocScannerFileNameTableCell.swift +++ b/iMEGA/Cloud drive/DocScannerFileNameTableCell.swift @@ -1,4 +1,3 @@ - import MEGADomain import UIKit diff --git a/iMEGA/Cloud drive/DocScannerSaveSettingTableViewController.swift b/iMEGA/Cloud drive/DocScannerSaveSettingTableViewController.swift index 4b945173a2..9875134316 100644 --- a/iMEGA/Cloud drive/DocScannerSaveSettingTableViewController.swift +++ b/iMEGA/Cloud drive/DocScannerSaveSettingTableViewController.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import PDFKit import UIKit import VisionKit @@ -400,17 +401,17 @@ extension DocScannerSaveSettingTableViewController: SendToViewControllerDelegate let appData = NSString().mnz_appData(toSaveCoordinates: path.mnz_coordinatesOfPhotoOrVideo() ?? "") let startUploadTransferDelegate = MEGAStartUploadTransferDelegate { (transfer) in guard let transferNodeHandle = transfer?.nodeHandle, - let nodeHandle = MEGASdkManager.sharedMEGASdk().node(forHandle: transferNodeHandle)?.handle else { return } + let nodeHandle = MEGASdk.shared.node(forHandle: transferNodeHandle)?.handle else { return } chats.forEach { chatRoom in - MEGASdkManager.sharedMEGAChatSdk().attachNode(toChat: chatRoom.chatId, node: nodeHandle) + MEGAChatSdk.shared.attachNode(toChat: chatRoom.chatId, node: nodeHandle) } users.forEach { user in - if let chatRoom = MEGASdkManager.sharedMEGAChatSdk().chatRoom(byUser: user.handle) { - MEGASdkManager.sharedMEGAChatSdk().attachNode(toChat: chatRoom.chatId, node: nodeHandle) + if let chatRoom = MEGAChatSdk.shared.chatRoom(byUser: user.handle) { + MEGAChatSdk.shared.attachNode(toChat: chatRoom.chatId, node: nodeHandle) } else { - MEGASdkManager.sharedMEGAChatSdk().mnz_createChatRoom(userHandle: user.handle, completion: { (chatRoom) in - MEGASdkManager.sharedMEGAChatSdk().attachNode(toChat: chatRoom.chatId, node: nodeHandle) + MEGAChatSdk.shared.mnz_createChatRoom(userHandle: user.handle, completion: { (chatRoom) in + MEGAChatSdk.shared.attachNode(toChat: chatRoom.chatId, node: nodeHandle) }) } } @@ -423,7 +424,7 @@ extension DocScannerSaveSettingTableViewController: SendToViewControllerDelegate } completionCounter += 1 } - MEGASdkManager.sharedMEGASdk().startUploadForChat(withLocalPath: path, parent: myChatFilesFolderNode, appData: appData, isSourceTemporary: true, fileName: nil, delegate: startUploadTransferDelegate!) + MEGASdk.shared.startUploadForChat(withLocalPath: path, parent: myChatFilesFolderNode, appData: appData, isSourceTemporary: true, fileName: nil, delegate: startUploadTransferDelegate!) } } dismiss(animated: true, completion: nil) diff --git a/iMEGA/Cloud drive/GetLink/Cells/GetLinkAccessInfoTableViewCell.swift b/iMEGA/Cloud drive/GetLink/Cells/GetLinkAccessInfoTableViewCell.swift index 2efbba5c76..d9f5118be8 100644 --- a/iMEGA/Cloud drive/GetLink/Cells/GetLinkAccessInfoTableViewCell.swift +++ b/iMEGA/Cloud drive/GetLink/Cells/GetLinkAccessInfoTableViewCell.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit class GetLinkAccessInfoTableViewCell: UITableViewCell { diff --git a/iMEGA/Cloud drive/GetLink/Cells/GetLinkAlbumInfoCellViewModel.swift b/iMEGA/Cloud drive/GetLink/Cells/GetLinkAlbumInfoCellViewModel.swift index ec1d461c54..af016e8967 100644 --- a/iMEGA/Cloud drive/GetLink/Cells/GetLinkAlbumInfoCellViewModel.swift +++ b/iMEGA/Cloud drive/GetLink/Cells/GetLinkAlbumInfoCellViewModel.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n import MEGAPresentation enum GetLinkInfoCellAction: ActionType { diff --git a/iMEGA/Cloud drive/GetLink/Cells/GetLinkDetailTableViewCell.swift b/iMEGA/Cloud drive/GetLink/Cells/GetLinkDetailTableViewCell.swift index c01b82ae21..36c21efffa 100644 --- a/iMEGA/Cloud drive/GetLink/Cells/GetLinkDetailTableViewCell.swift +++ b/iMEGA/Cloud drive/GetLink/Cells/GetLinkDetailTableViewCell.swift @@ -1,4 +1,5 @@ import MEGAFoundation +import MEGAL10n import UIKit class GetLinkDetailTableViewCell: UITableViewCell { diff --git a/iMEGA/Cloud drive/GetLink/Cells/GetLinkSwitchOptionTableViewCell.swift b/iMEGA/Cloud drive/GetLink/Cells/GetLinkSwitchOptionTableViewCell.swift index 88eb104a15..b8804e19e9 100644 --- a/iMEGA/Cloud drive/GetLink/Cells/GetLinkSwitchOptionTableViewCell.swift +++ b/iMEGA/Cloud drive/GetLink/Cells/GetLinkSwitchOptionTableViewCell.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit class GetLinkSwitchOptionTableViewCell: UITableViewCell { diff --git a/iMEGA/Cloud drive/GetLink/DecryptionKeysViewController.swift b/iMEGA/Cloud drive/GetLink/DecryptionKeysViewController.swift index 024bbd4371..f74ca9ed2f 100644 --- a/iMEGA/Cloud drive/GetLink/DecryptionKeysViewController.swift +++ b/iMEGA/Cloud drive/GetLink/DecryptionKeysViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit class DecryptionKeysViewController: UIViewController { diff --git a/iMEGA/Cloud drive/GetLink/GetAlbumLinkViewModel.swift b/iMEGA/Cloud drive/GetLink/GetAlbumLinkViewModel.swift index 349ee2b19e..3e7260ad39 100644 --- a/iMEGA/Cloud drive/GetLink/GetAlbumLinkViewModel.swift +++ b/iMEGA/Cloud drive/GetLink/GetAlbumLinkViewModel.swift @@ -1,5 +1,7 @@ import Foundation +import MEGAAnalyticsiOS import MEGADomain +import MEGAL10n import MEGAPresentation final class GetAlbumLinkViewModel: GetLinkViewModelType { @@ -7,6 +9,7 @@ final class GetAlbumLinkViewModel: GetLinkViewModelType { private let album: AlbumEntity private let shareAlbumUseCase: any ShareAlbumUseCaseProtocol + private let tracker: any AnalyticsTracking private var sectionViewModels = [GetLinkSectionViewModel]() private var shareLink: String? @@ -24,11 +27,14 @@ final class GetAlbumLinkViewModel: GetLinkViewModelType { return decryptCellViewModel.isSwitchOn } - init(album: AlbumEntity, shareAlbumUseCase: any ShareAlbumUseCaseProtocol, - sectionViewModels: [GetLinkSectionViewModel]) { + init(album: AlbumEntity, + shareAlbumUseCase: some ShareAlbumUseCaseProtocol, + sectionViewModels: [GetLinkSectionViewModel], + tracker: some AnalyticsTracking) { self.album = album self.shareAlbumUseCase = shareAlbumUseCase self.sectionViewModels = sectionViewModels + self.tracker = tracker } // MARK: - Dispatch action @@ -36,6 +42,7 @@ final class GetAlbumLinkViewModel: GetLinkViewModelType { func dispatch(_ action: GetLinkAction) { switch action { case .onViewReady: + tracker.trackAnalyticsEvent(with: SingleAlbumLinkScreenEvent()) updateViewConfiguration() loadAlbumLink() case .onViewWillDisappear: diff --git a/iMEGA/Cloud drive/GetLink/GetAlbumsLinksViewModel.swift b/iMEGA/Cloud drive/GetLink/GetAlbumsLinksViewModel.swift index ae5666b618..cb63fff8fc 100644 --- a/iMEGA/Cloud drive/GetLink/GetAlbumsLinksViewModel.swift +++ b/iMEGA/Cloud drive/GetLink/GetAlbumsLinksViewModel.swift @@ -1,5 +1,7 @@ import Foundation +import MEGAAnalyticsiOS import MEGADomain +import MEGAL10n import MEGAPresentation final class GetAlbumsLinkViewModel: GetLinkViewModelType { @@ -7,6 +9,7 @@ final class GetAlbumsLinkViewModel: GetLinkViewModelType { private let albums: [AlbumEntity] private let shareAlbumUseCase: any ShareAlbumUseCaseProtocol + private let tracker: any AnalyticsTracking private var sectionViewModels = [GetLinkSectionViewModel]() private var albumLinks: [HandleEntity: String]? private var loadingTask: Task? @@ -17,11 +20,14 @@ final class GetAlbumsLinkViewModel: GetLinkViewModelType { sectionViewModels.count } - init(albums: [AlbumEntity], shareAlbumUseCase: any ShareAlbumUseCaseProtocol, - sectionViewModels: [GetLinkSectionViewModel]) { + init(albums: [AlbumEntity], + shareAlbumUseCase: some ShareAlbumUseCaseProtocol, + sectionViewModels: [GetLinkSectionViewModel], + tracker: some AnalyticsTracking) { self.albums = albums self.shareAlbumUseCase = shareAlbumUseCase self.sectionViewModels = sectionViewModels + self.tracker = tracker } // MARK: - Dispatch action @@ -29,6 +35,7 @@ final class GetAlbumsLinkViewModel: GetLinkViewModelType { func dispatch(_ action: GetLinkAction) { switch action { case .onViewReady: + tracker.trackAnalyticsEvent(with: MultipleAlbumLinksScreenEvent()) updateViewConfiguration() loadLinksForAlbums() case .onViewWillDisappear: diff --git a/iMEGA/Cloud drive/GetLink/GetAlbumsLinksViewWrapper.swift b/iMEGA/Cloud drive/GetLink/GetAlbumsLinksViewWrapper.swift index d573893dc0..f68bd4e776 100644 --- a/iMEGA/Cloud drive/GetLink/GetAlbumsLinksViewWrapper.swift +++ b/iMEGA/Cloud drive/GetLink/GetAlbumsLinksViewWrapper.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAPresentation import MEGASDKRepo import SwiftUI @@ -21,13 +22,15 @@ struct GetAlbumsLinksViewWrapper: UIViewControllerRepresentable { let initialSections = ShareAlbumLinkInitialSections(album: album) return GetAlbumLinkViewModel(album: album, shareAlbumUseCase: shareAlbumUseCase, - sectionViewModels: initialSections.initialLinkSectionViewModels) + sectionViewModels: initialSections.initialLinkSectionViewModels, + tracker: DIContainer.tracker) } let initialSections = ShareAlbumsLinkInitialSections(albums: albums, thumbnailUseCase: ThumbnailUseCase(repository: ThumbnailRepository.newRepo)) return GetAlbumsLinkViewModel(albums: albums, shareAlbumUseCase: shareAlbumUseCase, - sectionViewModels: initialSections.initialLinkSectionViewModels) + sectionViewModels: initialSections.initialLinkSectionViewModels, + tracker: DIContainer.tracker) } func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {} diff --git a/iMEGA/Cloud drive/GetLink/GetLinkViewController.swift b/iMEGA/Cloud drive/GetLink/GetLinkViewController.swift index d06235cd3b..47989cbda7 100644 --- a/iMEGA/Cloud drive/GetLink/GetLinkViewController.swift +++ b/iMEGA/Cloud drive/GetLink/GetLinkViewController.swift @@ -1,5 +1,6 @@ import MEGADomain import MEGAFoundation +import MEGAL10n import MEGASDKRepo import UIKit @@ -187,8 +188,8 @@ class GetLinkViewController: UIViewController { } private func loadNodes() { - if !MEGASdkManager.sharedMEGASdk().mnz_isProAccount { - MEGASdkManager.sharedMEGASdk().add(self as any MEGARequestDelegate) + if !MEGASdk.shared.mnz_isProAccount { + MEGASdk.shared.add(self as any MEGARequestDelegate) MEGAPurchase.sharedInstance()?.purchaseDelegateMutableArray.add(self) MEGAPurchase.sharedInstance()?.restoreDelegateMutableArray.add(self) @@ -206,7 +207,7 @@ class GetLinkViewController: UIViewController { super.viewWillDisappear(animated) SVProgressHUD.dismiss() - if isMovingFromParent && !MEGASdkManager.sharedMEGASdk().mnz_isProAccount { + if isMovingFromParent && !MEGASdk.shared.mnz_isProAccount { removeDelegates() } @@ -360,9 +361,9 @@ class GetLinkViewController: UIViewController { } private func exportNode(node: MEGANode) { - MEGASdkManager.sharedMEGASdk().export(node, delegate: MEGAExportRequestDelegate.init(completion: { [weak self] _ in + MEGASdk.shared.export(node, delegate: MEGAExportRequestDelegate.init(completion: { [weak self] _ in (self?.nodesToExportCount -= 1) - guard let nodeUpdated = MEGASdkManager.sharedMEGASdk().node(forHandle: node.handle) else { + guard let nodeUpdated = MEGASdk.shared.node(forHandle: node.handle) else { return } self?.updateModel(forNode: nodeUpdated) @@ -434,7 +435,7 @@ class GetLinkViewController: UIViewController { } private func encrypt(link: String, with password: String) { - MEGASdkManager.sharedMEGASdk().encryptLink(withPassword: link, password: password, delegate: MEGAPasswordLinkRequestDelegate.init(completion: {(request) in + MEGASdk.shared.encryptLink(withPassword: link, password: password, delegate: MEGAPasswordLinkRequestDelegate.init(completion: {(request) in SVProgressHUD.dismiss() guard let encryptedLink = request?.text else { return @@ -452,8 +453,8 @@ class GetLinkViewController: UIViewController { } private func setExpiryDate() { - MEGASdkManager.sharedMEGASdk().export(nodes[0], expireTime: getLinkVM.date ?? Date(timeInterval: 24*60*60, since: Date()), delegate: MEGAExportRequestDelegate.init(completion: { [weak self] request in - guard let nodeHandle = request?.nodeHandle, let nodeUpdated = MEGASdkManager.sharedMEGASdk().node(forHandle: nodeHandle) else { + MEGASdk.shared.export(nodes[0], expireTime: getLinkVM.date ?? Date(timeInterval: 24*60*60, since: Date()), delegate: MEGAExportRequestDelegate.init(completion: { [weak self] request in + guard let nodeHandle = request?.nodeHandle, let nodeUpdated = MEGASdk.shared.node(forHandle: nodeHandle) else { return } SVProgressHUD.dismiss() @@ -507,7 +508,7 @@ class GetLinkViewController: UIViewController { } private func removeDelegates() { - MEGASdkManager.sharedMEGASdk().remove(self as any MEGARequestDelegate) + MEGASdk.shared.remove(self as any MEGARequestDelegate) MEGAPurchase.sharedInstance()?.purchaseDelegateMutableArray.remove(self) MEGAPurchase.sharedInstance()?.restoreDelegateMutableArray.remove(self) @@ -545,7 +546,7 @@ class GetLinkViewController: UIViewController { return } - if MEGASdkManager.sharedMEGASdk().mnz_isProAccount { + if MEGASdk.shared.mnz_isProAccount { configureExpiryDate(isOn: sender.isOn) } else { showUpgradeToProCustomModalAlert() @@ -660,7 +661,7 @@ class GetLinkViewController: UIViewController { fatalError("Could not get GetLinkSwitchOptionTableViewCell") } - cell.configureActivateExpiryDateCell(isOn: getLinkVM.expiryDate, isPro: MEGASdkManager.sharedMEGASdk().mnz_isProAccount, justUpgraded: justUpgradedToProAccount) + cell.configureActivateExpiryDateCell(isOn: getLinkVM.expiryDate, isPro: MEGASdk.shared.mnz_isProAccount, justUpgraded: justUpgradedToProAccount) return cell } @@ -690,7 +691,7 @@ class GetLinkViewController: UIViewController { fatalError("Could not get GetLinkDetailTableViewCell") } - cell.configurePasswordCell(passwordActive: getLinkVM.passwordProtect, isPro: MEGASdkManager.sharedMEGASdk().mnz_isProAccount, justUpgraded: justUpgradedToProAccount) + cell.configurePasswordCell(passwordActive: getLinkVM.passwordProtect, isPro: MEGASdk.shared.mnz_isProAccount, justUpgraded: justUpgradedToProAccount) return cell } @@ -1088,7 +1089,7 @@ extension GetLinkViewController: UITableViewDelegate { return } - if MEGASdkManager.sharedMEGASdk().mnz_isProAccount { + if MEGASdk.shared.mnz_isProAccount { getLinkVM.trackSetPassword() present(SetLinkPasswordViewController.instantiate(withDelegate: self), animated: true, completion: nil) } else { diff --git a/iMEGA/Cloud drive/GetLink/SetLinkPasswordViewController.swift b/iMEGA/Cloud drive/GetLink/SetLinkPasswordViewController.swift index 82d3865120..e8d70336cb 100644 --- a/iMEGA/Cloud drive/GetLink/SetLinkPasswordViewController.swift +++ b/iMEGA/Cloud drive/GetLink/SetLinkPasswordViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit protocol SetLinkPasswordViewControllerDelegate { @@ -83,7 +84,7 @@ class SetLinkPasswordViewController: UIViewController { if password.mnz_isEmpty() { passwordView.setErrorState(true, withText: Strings.Localizable.passwordInvalidFormat) return false - } else if MEGASdkManager.sharedMEGASdk().passwordStrength(password as String) == .veryWeak { + } else if MEGASdk.shared.passwordStrength(password as String) == .veryWeak { passwordView.setErrorState(true, withText: Strings.Localizable.pleaseStrengthenYourPassword) return false } else { @@ -129,7 +130,7 @@ extension SetLinkPasswordViewController: UITextFieldDelegate { } else { passwordStrenghtIndicatorHeightConstraint.constant = 44.5 passwordView.setErrorState(false, withText: Strings.Localizable.passwordPlaceholder) - passwordStrenghtIndicatorView.update(with: MEGASdkManager.sharedMEGASdk().passwordStrength(text), updateDescription: false) + passwordStrenghtIndicatorView.update(with: MEGASdk.shared.passwordStrength(text), updateDescription: false) } } else if textField == confirmPasswordView.passwordTextField { confirmPasswordView.setErrorState(false, withText: Strings.Localizable.confirmPassword) diff --git a/iMEGA/Cloud drive/GetLink/ShareAlbumLinkInitialSections.swift b/iMEGA/Cloud drive/GetLink/ShareAlbumLinkInitialSections.swift index d069d23171..3bc8e73f14 100644 --- a/iMEGA/Cloud drive/GetLink/ShareAlbumLinkInitialSections.swift +++ b/iMEGA/Cloud drive/GetLink/ShareAlbumLinkInitialSections.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo struct ShareAlbumLinkInitialSections { diff --git a/iMEGA/Cloud drive/MediaDiscovery/MediaDiscoveryViewController.swift b/iMEGA/Cloud drive/MediaDiscovery/MediaDiscoveryViewController.swift index 52a79673ff..90d011cac9 100644 --- a/iMEGA/Cloud drive/MediaDiscovery/MediaDiscoveryViewController.swift +++ b/iMEGA/Cloud drive/MediaDiscovery/MediaDiscoveryViewController.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPermissions import UIKit diff --git a/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoActionTableViewCell.swift b/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoActionTableViewCell.swift index 8b97eb67f2..bad1359e53 100644 --- a/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoActionTableViewCell.swift +++ b/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoActionTableViewCell.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit class NodeInfoActionTableViewCell: UITableViewCell { diff --git a/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoDetailTableViewCell.swift b/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoDetailTableViewCell.swift index f388048ad2..85ab32dc73 100644 --- a/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoDetailTableViewCell.swift +++ b/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoDetailTableViewCell.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit class NodeInfoDetailTableViewCell: UITableViewCell { @@ -40,7 +41,7 @@ class NodeInfoDetailTableViewCell: UITableViewCell { private func configureAsLocation(withNode node: MEGANode) { keyLabel.text = Strings.Localizable.CloudDrive.Info.Node.location - guard let parentNode = MEGASdkManager.sharedMEGASdk().parentNode(for: node) else { + guard let parentNode = MEGASdk.shared.parentNode(for: node) else { return } if parentNode.type == .root { @@ -61,12 +62,12 @@ class NodeInfoDetailTableViewCell: UITableViewCell { private func configureAsFileSize(withNode node: MEGANode) { keyLabel.text = Strings.Localizable.totalSize - valueLabel.text = node.mnz_numberOfVersions() == 0 ? Helper.size(for: node, api: MEGASdkManager.sharedMEGASdk()) : String.memoryStyleString(fromByteCount: node.mnz_versionsSize()) + valueLabel.text = node.mnz_numberOfVersions() == 0 ? Helper.size(for: node, api: MEGASdk.shared) : String.memoryStyleString(fromByteCount: node.mnz_versionsSize()) } private func configureAsFileVersionSize(withNode node: MEGANode) { keyLabel.text = Strings.Localizable.currentVersion - valueLabel.text = Helper.size(for: node, api: MEGASdkManager.sharedMEGASdk()) + valueLabel.text = Helper.size(for: node, api: MEGASdk.shared) } private func configureAsFileType(withNode node: MEGANode) { diff --git a/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoPreviewTableViewCell.swift b/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoPreviewTableViewCell.swift index a4dc6eb21f..4ea958a7f1 100644 --- a/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoPreviewTableViewCell.swift +++ b/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoPreviewTableViewCell.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import UIKit class NodeInfoPreviewTableViewCell: UITableViewCell { @@ -18,13 +19,13 @@ class NodeInfoPreviewTableViewCell: UITableViewCell { linkedView.isHidden = !node.isExported() if node.type == .file { previewImage.mnz_setThumbnail(by: node) - sizeLabel.text = Helper.size(for: node, api: MEGASdkManager.sharedMEGASdk()) + sizeLabel.text = Helper.size(for: node, api: MEGASdk.shared) shareStackView.isHidden = true - versionedView.isHidden = !MEGASdkManager.sharedMEGASdk().hasVersions(for: node) + versionedView.isHidden = !MEGASdk.shared.hasVersions(for: node) playIconImage.isHidden = node.name?.fileExtensionGroup.isVideo != true } else if node.type == .folder { previewImage.image = NodeAssetsManager.shared.icon(for: node) - let nodeAccess = MEGASdkManager.sharedMEGASdk().accessLevel(for: node) + let nodeAccess = MEGASdk.shared.accessLevel(for: node) shareStackView.isHidden = isNodeInRubbish || (nodeAccess != .accessOwner) shareButton.setTitle(Strings.Localizable.General.share.localizedUppercase, for: .normal) let folderSize = folderInfo?.currentSize ?? 0 diff --git a/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoVerifyAccountTableViewCell.swift b/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoVerifyAccountTableViewCell.swift index f86db18d21..60972ae6e6 100644 --- a/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoVerifyAccountTableViewCell.swift +++ b/iMEGA/Cloud drive/NodeInfo/Cells/NodeInfoVerifyAccountTableViewCell.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct NodeInfoVerifyAccountTableViewCell: View { diff --git a/iMEGA/Cloud drive/NodeInfo/Cells/NodeOwnerInfoTableViewCell.swift b/iMEGA/Cloud drive/NodeInfo/Cells/NodeOwnerInfoTableViewCell.swift index c1457c5f2d..6b2ca7d42f 100644 --- a/iMEGA/Cloud drive/NodeInfo/Cells/NodeOwnerInfoTableViewCell.swift +++ b/iMEGA/Cloud drive/NodeInfo/Cells/NodeOwnerInfoTableViewCell.swift @@ -1,4 +1,5 @@ import MEGAChatSdk +import MEGAL10n import UIKit class NodeOwnerInfoTableViewCell: UITableViewCell { diff --git a/iMEGA/Cloud drive/NodeInfo/NodeInfoViewController.swift b/iMEGA/Cloud drive/NodeInfo/NodeInfoViewController.swift index c9b78a8c78..4e15556c5a 100644 --- a/iMEGA/Cloud drive/NodeInfo/NodeInfoViewController.swift +++ b/iMEGA/Cloud drive/NodeInfo/NodeInfoViewController.swift @@ -1,5 +1,5 @@ - import MEGADomain +import MEGAL10n import MEGASDKRepo import MEGASwiftUI import UIKit diff --git a/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController+Additions.swift b/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController+Additions.swift index aa1c3bc353..906ff41788 100644 --- a/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController+Additions.swift +++ b/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController+Additions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n extension NodeVersionsViewController { @objc func setToolbarActionsEnabled(_ boolValue: Bool) { @@ -17,4 +18,12 @@ extension NodeVersionsViewController { setToolbarItems(isBackupNode ? [downloadBarButtonItem, flexibleItem, removeBarButtonItem] : [downloadBarButtonItem, flexibleItem, revertBarButtonItem, flexibleItem, removeBarButtonItem], animated: true) } + + @objc func selectedCountTitle() -> String { + guard let selectedCount = selectedNodesArray?.count, + selectedCount > 0 else { + return Strings.Localizable.selectTitle + } + return Strings.Localizable.General.Format.itemsSelected(selectedCount) + } } diff --git a/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController.h b/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController.h index 597b86666f..c83525ec8a 100644 --- a/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController.h +++ b/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController.h @@ -1,4 +1,3 @@ - #import @interface NodeVersionsViewController : UIViewController diff --git a/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController.m b/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController.m index 5f794db72a..8092dc95ab 100644 --- a/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController.m +++ b/iMEGA/Cloud drive/NodeInfo/NodeVersionsViewController.m @@ -1,4 +1,3 @@ - #import "NodeVersionsViewController.h" #import "SVProgressHUD.h" @@ -15,6 +14,7 @@ #import "MEGAPhotoBrowserViewController.h" +@import MEGAL10nObjc; @import MEGASDKRepo; @interface NodeVersionsViewController () { @@ -39,9 +39,9 @@ @implementation NodeVersionsViewController - (void)viewDidLoad { [super viewDidLoad]; - self.title = NSLocalizedString(@"versions", @"Title of section to display number of all historical versions of files."); - self.editBarButtonItem.title = NSLocalizedString(@"select", @"Caption of a button to select files"); - self.closeBarButtonItem.title = NSLocalizedString(@"close", @"A button label."); + self.title = LocalizedString(@"versions", @"Title of section to display number of all historical versions of files."); + self.editBarButtonItem.title = LocalizedString(@"select", @"Caption of a button to select files"); + self.closeBarButtonItem.title = LocalizedString(@"close", @"A button label."); [self configureToolbarItems]; self.tableView.tableFooterView = [UIView.alloc initWithFrame:CGRectZero]; @@ -215,9 +215,9 @@ - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger GenericHeaderFooterView *sectionHeader = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:@"GenericHeaderFooterViewID"]; if (section == 0) { - [sectionHeader configureWithTitle:NSLocalizedString(@"currentVersion", @"Title of section to display information of the current version of a file") topDistance:30.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; + [sectionHeader configureWithTitle:LocalizedString(@"currentVersion", @"Title of section to display information of the current version of a file") topDistance:30.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; } else { - [sectionHeader configureWithTitle:NSLocalizedString(@"previousVersions", @"A button label which opens a dialog to display the full version history of the selected file") detail:[NSString memoryStyleStringFromByteCount:self.node.mnz_versionsSize] topDistance:30.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; + [sectionHeader configureWithTitle:LocalizedString(@"previousVersions", @"A button label which opens a dialog to display the full version history of the selected file") detail:[NSString memoryStyleStringFromByteCount:self.node.mnz_versionsSize] topDistance:30.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; } return sectionHeader; } @@ -278,13 +278,9 @@ - (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwip - (void)updateNavigationBarTitle { NSString *navigationTitle; if (self.tableView.isEditing) { - if (self.selectedNodesArray.count == 0) { - navigationTitle = NSLocalizedString(@"selectTitle", @"Title shown on the Camera Uploads section when the edit mode is enabled. On this mode you can select photos"); - } else { - navigationTitle = (self.selectedNodesArray.count <= 1) ? [NSString stringWithFormat:NSLocalizedString(@"oneItemSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected one photo"), self.selectedNodesArray.count] : [NSString stringWithFormat:NSLocalizedString(@"itemsSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected more than one photo"), self.selectedNodesArray.count]; - } + navigationTitle = [self selectedCountTitle]; } else { - navigationTitle = NSLocalizedString(@"versions", @"Title of section to display number of all historical versions of files."); + navigationTitle = LocalizedString(@"versions", @"Title of section to display number of all historical versions of files."); } self.navigationItem.title = navigationTitle; @@ -297,7 +293,7 @@ - (void)setEditing:(BOOL)editing animated:(BOOL)animated { [self updateNavigationBarTitle]; if (editing) { - self.editBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.editBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); self.navigationItem.rightBarButtonItems = @[self.editBarButtonItem]; self.navigationItem.leftBarButtonItems = @[self.selectAllBarButtonItem]; [self.navigationController setToolbarHidden:NO animated:YES]; @@ -308,7 +304,7 @@ - (void)setEditing:(BOOL)editing animated:(BOOL)animated { cell.selectedBackgroundView = view; } } else { - self.editBarButtonItem.title = NSLocalizedString(@"select", @"Caption of a button to select files"); + self.editBarButtonItem.title = LocalizedString(@"select", @"Caption of a button to select files"); allNodesSelected = NO; self.selectedNodesArray = nil; @@ -352,12 +348,12 @@ - (IBAction)revertAction:(id)sender { MEGANode *node = self.selectedNodesArray.firstObject; if ([MEGASdkManager.sharedMEGASdk accessLevelForNode:node] == MEGAShareTypeAccessReadWrite) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"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:NSLocalizedString(@"You do not have the permissions required to revert this file. In order to continue, we can create a new file with the reverted data. Would you like to proceed?", @"Confirmation dialog shown to user when they try to revert a node in an incoming ReadWrite share.") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Create new file", @"Text shown for the action create new file") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"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:LocalizedString(@"You do not have the permissions required to revert this file. In order to continue, we can create a new file with the reverted data. Would you like to proceed?", @"Confirmation dialog shown to user when they try to revert a node in an incoming ReadWrite share.") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"Create new file", @"Text shown for the action create new file") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [MEGASdkManager.sharedMEGASdk restoreVersionNode:node delegate:[MEGAGenericRequestDelegate.alloc initWithCompletion:^(MEGARequest * _Nonnull request, MEGAError * _Nonnull error) { if (error.type == MEGAErrorTypeApiOk) { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"Version created as a new file successfully.", @"Text shown when the creation of a version as a new file was successful")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"Version created as a new file successfully.", @"Text shown when the creation of a version as a new file was successful")]; } }]]; }]]; @@ -370,9 +366,9 @@ - (IBAction)revertAction:(id)sender { } - (IBAction)removeAction:(id)sender { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"deleteVersion", @"Question to ensure user wants to delete file version") message:NSLocalizedString(@"permanentlyRemoved", @"Message to notify user the file version will be permanently removed") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"delete", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"deleteVersion", @"Question to ensure user wants to delete file version") message:LocalizedString(@"permanentlyRemoved", @"Message to notify user the file version will be permanently removed") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"delete", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { for (MEGANode *node in self.selectedNodesArray) { [[MEGASdkManager sharedMEGASdk] removeVersionNode:node]; } @@ -516,12 +512,12 @@ - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error: if (error.type) { if (error.type == MEGAErrorTypeApiEAccess) { if (transfer.type == MEGATransferTypeUpload) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"permissionTitle", nil) message:NSLocalizedString(@"permissionMessage", nil) preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"permissionTitle", @"") message:LocalizedString(@"permissionMessage", @"") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; } } else if (error.type == MEGAErrorTypeApiEIncomplete) { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudMinus"] status:NSLocalizedString(@"transferCancelled", nil)]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudMinus"] status:LocalizedString(@"transferCancelled", @"")]; NSString *base64Handle = [MEGASdk base64HandleForHandle:transfer.nodeHandle]; NSIndexPath *indexPath = [self.nodesIndexPathMutableDictionary objectForKey:base64Handle]; if (indexPath != nil) { diff --git a/iMEGA/Cloud drive/Operations/SearchOperation+Initialize.swift b/iMEGA/Cloud drive/Operations/SearchOperation+Initialize.swift index cc9bf059fd..14b08826ba 100644 --- a/iMEGA/Cloud drive/Operations/SearchOperation+Initialize.swift +++ b/iMEGA/Cloud drive/Operations/SearchOperation+Initialize.swift @@ -7,7 +7,7 @@ extension SearchOperation { cancelToken: MEGACancelToken, sortOrderType: MEGASortOrderType, nodeFormatType: MEGANodeFormatType, - completion: @escaping (Result<[MEGANode], Error>) -> Void) { + completion: @escaping (Result<[MEGANode], any Error>) -> Void) { self.init( parentNode: node, text: text, diff --git a/iMEGA/Cloud drive/Operations/SearchOperation.h b/iMEGA/Cloud drive/Operations/SearchOperation.h index 9641eba417..d73db10cb4 100644 --- a/iMEGA/Cloud drive/Operations/SearchOperation.h +++ b/iMEGA/Cloud drive/Operations/SearchOperation.h @@ -1,4 +1,3 @@ - #import "MEGAOperation.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Cloud drive/Operations/SearchOperation.m b/iMEGA/Cloud drive/Operations/SearchOperation.m index 4e6cd855eb..7ae23b4b12 100644 --- a/iMEGA/Cloud drive/Operations/SearchOperation.m +++ b/iMEGA/Cloud drive/Operations/SearchOperation.m @@ -1,4 +1,3 @@ - #import "SearchOperation.h" #import "Helper.h" diff --git a/iMEGA/Extensions/MEGAIntent/IntentHandler.swift b/iMEGA/Extensions/MEGAIntent/IntentHandler.swift index 11f1a5f227..dfe71302d5 100644 --- a/iMEGA/Extensions/MEGAIntent/IntentHandler.swift +++ b/iMEGA/Extensions/MEGAIntent/IntentHandler.swift @@ -1,21 +1,19 @@ import Firebase import Intents +import MEGAIntentDomain +import MEGARepo final class IntentHandler: INExtension { - lazy var personProvider = IntentPersonProvider() - + lazy var intentPersonUseCase = IntentPersonUseCase(repository: ContactsRepository.newRepo) + override init() { super.init() FirebaseApp.configure() } - + override func handler(for intent: INIntent) -> Any { - // This is the default implementation. If you want different objects to handle different intents, - // you can override this and return the handler you want for that particular intent. - - return self + self } - } extension IntentHandler: SelectShortcutIntentHandling { @@ -27,7 +25,7 @@ extension IntentHandler: SelectShortcutIntentHandling { } func defaultShortcut(for intent: SelectShortcutIntent) -> [IntentShortcut]? { - return ShortcutDetail.availableShortcuts.map { + ShortcutDetail.availableShortcuts.map { IntentShortcut(identifier: $0.link, display: $0.title) } } diff --git a/iMEGA/Extensions/MEGAIntent/StartCall/IntentHandler+StartCall.swift b/iMEGA/Extensions/MEGAIntent/StartCall/IntentHandler+StartCall.swift index 7be6fe0806..a1b290c026 100644 --- a/iMEGA/Extensions/MEGAIntent/StartCall/IntentHandler+StartCall.swift +++ b/iMEGA/Extensions/MEGAIntent/StartCall/IntentHandler+StartCall.swift @@ -1,7 +1,7 @@ import Contacts import Intents import MEGADomain -import MEGASdk +import MEGARepo import MEGASDKRepo extension IntentHandler: INStartCallIntentHandling { @@ -19,7 +19,7 @@ extension IntentHandler: INStartCallIntentHandling { func resolveContacts(for intent: INStartCallIntent) async -> [INStartCallContactResolutionResult] { let credentialUseCase = CredentialUseCase(repo: CredentialRepository.newRepo) - + guard credentialUseCase.hasSession() else { return [.unsupported(forReason: .invalidHandle)] } @@ -44,22 +44,48 @@ extension IntentHandler: INStartCallIntentHandling { if personHasEmail { return [.success(with: person)] } - - let authorizedToUseContacts = CNContactStore.authorizationStatus(for: .contacts) == .authorized - guard authorizedToUseContacts else { + + let authorizationStatusForContacts = CNContactStore.authorizationStatus(for: .contacts) + + switch authorizationStatusForContacts { + case .notDetermined: + return await handleUndeterminedAuthorizationToContacts(for: person) + case .restricted, .denied: + return [.unsupported(forReason: .noContactFound)] + case .authorized: + return processResolutionInContacts(for: person) + @unknown default: return [.unsupported(forReason: .noContactFound)] } - - let persons = personProvider.personsInContacts(person) - + } + + func handleUndeterminedAuthorizationToContacts(for person: INPerson) async -> [INStartCallContactResolutionResult] { + let store = CNContactStore() + + do { + let isAuthorized = try await store.requestAccess(for: .contacts) + + guard isAuthorized else { + return [.unsupported(forReason: .noContactFound)] + } + + return processResolutionInContacts(for: person) + } catch { + return [.unsupported(forReason: .noContactFound)] + } + } + + func processResolutionInContacts(for person: INPerson) -> [INStartCallContactResolutionResult] { + let persons = intentPersonUseCase.personsInContacts(matching: person) + guard persons.isNotEmpty else { return [.unsupported(forReason: .noContactFound)] } - + if persons.count == 1, let person = persons.first { return [.success(with: person)] } - + return [.disambiguation(with: persons)] } } diff --git a/iMEGA/Extensions/MEGAIntent/StartCall/IntentPersonProvider.swift b/iMEGA/Extensions/MEGAIntent/StartCall/IntentPersonProvider.swift deleted file mode 100644 index 6f9ce20ed4..0000000000 --- a/iMEGA/Extensions/MEGAIntent/StartCall/IntentPersonProvider.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Contacts -import Intents - -struct IntentPersonProvider { - private let contactStore = CNContactStore() - - func personsInContacts(_ person: INPerson) -> [INPerson] { - var contacts: [CNContact] = [] - - let keys: [CNKeyDescriptor] = [ - CNContactGivenNameKey as CNKeyDescriptor, - CNContactFamilyNameKey as CNKeyDescriptor, - CNContactEmailAddressesKey as CNKeyDescriptor - ] - - do { - try contactStore.enumerateContacts(with: CNContactFetchRequest(keysToFetch: keys)) { contact, _ -> Void in - guard !contact.emailAddresses.isEmpty else { return } - - let personDisplayName = person.displayName.lowercased() - let contactFirstName = contact.givenName.lowercased() - let contactLastName = contact.familyName.lowercased() - - if personDisplayName.lowercased().contains(contactFirstName.lowercased()) || - personDisplayName.lowercased().contains(contactLastName.lowercased()) { - contacts.append(contact) - } - } - } catch { - assertionFailure("Unable to fetch contacts.") - } - - let filteredContacts = contacts.filter { - person.displayName.lowercased().contains($0.givenName.lowercased()) && person.displayName.lowercased().contains($0.familyName.lowercased()) - } - - if filteredContacts.isNotEmpty { - contacts = filteredContacts - } - - let persons = contacts.persons(withDisplayName: person.displayName) - - return persons.removeDuplicatesWhileKeepingTheOriginalOrder() - } -} diff --git a/iMEGA/Extensions/MEGANotifications/MEGANotifications-Bridging-Header.h b/iMEGA/Extensions/MEGANotifications/MEGANotifications-Bridging-Header.h index 856a6aae3e..6bee525bb8 100644 --- a/iMEGA/Extensions/MEGANotifications/MEGANotifications-Bridging-Header.h +++ b/iMEGA/Extensions/MEGANotifications/MEGANotifications-Bridging-Header.h @@ -1,4 +1,3 @@ - #ifndef MEGANotifications_Bridging_Header_h #define MEGANotifications_Bridging_Header_h diff --git a/iMEGA/Extensions/MEGANotifications/MEGANotifications-PrefixHeader.pch b/iMEGA/Extensions/MEGANotifications/MEGANotifications-PrefixHeader.pch index 16f2c2757a..0ed200ae93 100644 --- a/iMEGA/Extensions/MEGANotifications/MEGANotifications-PrefixHeader.pch +++ b/iMEGA/Extensions/MEGANotifications/MEGANotifications-PrefixHeader.pch @@ -1,4 +1,3 @@ - #ifndef MEGANotifications_PrefixHeader_h #define MEGANotifications_PrefixHeader_h diff --git a/iMEGA/Extensions/MEGANotifications/NotificationService+ScheduleMeeting.swift b/iMEGA/Extensions/MEGANotifications/NotificationService+ScheduleMeeting.swift index f2c05229e6..a8dbd8a400 100644 --- a/iMEGA/Extensions/MEGANotifications/NotificationService+ScheduleMeeting.swift +++ b/iMEGA/Extensions/MEGANotifications/NotificationService+ScheduleMeeting.swift @@ -1,3 +1,4 @@ +import MEGAL10n extension NotificationService { func processStartScheduledMeetingNotification(withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void, request: UNNotificationRequest) { @@ -36,7 +37,7 @@ extension NotificationService { } private func bodyForStartScheduledMeetingNotification(withInfo notificationInfo: ScheduleMeetingNotificationInfo) -> String { - NSLocalizedString( + Strings.localized( notificationInfo.startTime == .now ? "meetings.scheduleMeeting.notification.meetingStartsNow.message" : "meetings.scheduleMeeting.notification.meetingStartsInFifteenMins.message", diff --git a/iMEGA/Extensions/MEGANotifications/NotificationService.swift b/iMEGA/Extensions/MEGANotifications/NotificationService.swift index f5c8751956..ea901e6e56 100644 --- a/iMEGA/Extensions/MEGANotifications/NotificationService.swift +++ b/iMEGA/Extensions/MEGANotifications/NotificationService.swift @@ -1,5 +1,7 @@ +import ChatRepo import Firebase import MEGADomain +import MEGAL10n import MEGASDKRepo import SAMKeychain import UserNotifications @@ -82,7 +84,7 @@ class NotificationService: UNNotificationServiceExtension, MEGAChatNotificationD override func serviceExtensionTimeWillExpire() { MEGALogDebug("Service extension time will expire") - if let chatId = chatId, let msgId = msgId, let message = MEGASdkManager.sharedMEGAChatSdk().message(forChat: chatId, messageId: msgId) { + if let chatId = chatId, let msgId = msgId, let message = MEGAChatSdk.shared.message(forChat: chatId, messageId: msgId) { if message.type == .unknown { postNotification(withError: "Unknown message") } else { @@ -98,7 +100,7 @@ class NotificationService: UNNotificationServiceExtension, MEGAChatNotificationD // MARK: - Private private func generateNotification(with message: MEGAChatMessage, immediately: Bool) -> Bool { - guard let chatId = chatId, let chatRoom = MEGASdkManager.sharedMEGAChatSdk().chatRoom(forChatId: chatId) else { + guard let chatId = chatId, let chatRoom = MEGAChatSdk.shared.chatRoom(forChatId: chatId) else { return false } let notificationManager = MEGALocalNotificationManager(chatRoom: chatRoom, message: message) @@ -121,11 +123,9 @@ class NotificationService: UNNotificationServiceExtension, MEGAChatNotificationD var readyToPost = true if displayName == nil { - MEGASdkManager.sharedMEGAChatSdk().loadUserAttributes(forChatId: chatId, usersHandles: [message.userHandle] as [NSNumber], delegate: MEGAChatGenericRequestDelegate { [weak self] _, error in - if error.type != .MEGAChatErrorTypeOk { - return - } - guard let chatRoom = MEGASdkManager.sharedMEGAChatSdk().chatRoom(forChatId: chatId) else { + MEGAChatSdk.shared.loadUserAttributes(forChatId: chatId, usersHandles: [message.userHandle] as [NSNumber], delegate: ChatRequestDelegate { [weak self] result in + guard case .success = result, + let chatRoom = MEGAChatSdk.shared.chatRoom(forChatId: chatId) else { return } let displayName = chatRoom.userDisplayName(forUserHandle: message.userHandle) @@ -156,7 +156,11 @@ class NotificationService: UNNotificationServiceExtension, MEGAChatNotificationD return readyToPost } - MEGASdk.sharedNSE.getThumbnailNode(node, destinationFilePath: destinationFilePath, delegate: MEGAGenericRequestDelegate { [weak self] request, _ in + MEGASdk.sharedNSE.getThumbnailNode(node, destinationFilePath: destinationFilePath, delegate: RequestDelegate { [weak self] result in + guard case let .success(request) = result else { + return + } + if let base64Handle = node.base64Handle, let notificationAttachment = notificationManager.notificationAttachment(for: request.file, withIdentifier: base64Handle) { self?.bestAttemptContent?.attachments = [notificationAttachment] @@ -174,7 +178,7 @@ class NotificationService: UNNotificationServiceExtension, MEGAChatNotificationD } private func postNotification(withError error: String?, message: MEGAChatMessage? = nil) { - MEGASdkManager.sharedMEGAChatSdk().remove(self as MEGAChatNotificationDelegate) + MEGAChatSdk.shared.remove(self as MEGAChatNotificationDelegate) guard let contentHandler = contentHandler else { return @@ -196,7 +200,7 @@ class NotificationService: UNNotificationServiceExtension, MEGAChatNotificationD } } - MEGASdkManager.sharedMEGAChatSdk().saveCurrentState() + MEGAChatSdk.shared.saveCurrentState() // Note: As soon as we call the contentHandler, no content can be retrieved from notification center. if let sharedUserDefaults = UserDefaults(suiteName: MEGAGroupIdentifier) { @@ -255,7 +259,7 @@ class NotificationService: UNNotificationServiceExtension, MEGAChatNotificationD } } - if let message = MEGASdkManager.sharedMEGAChatSdk().message(forChat: chatId, messageId: msgId) { + if let message = MEGAChatSdk.shared.message(forChat: chatId, messageId: msgId) { if message.type != .unknown && generateNotification(with: message, immediately: false) { MEGALogDebug("Message exists in karere cache") postNotification(withError: nil, message: message) @@ -263,24 +267,27 @@ class NotificationService: UNNotificationServiceExtension, MEGAChatNotificationD } } - MEGASdkManager.sharedMEGAChatSdk().add(self as MEGAChatNotificationDelegate) - MEGASdkManager.sharedMEGAChatSdk().pushReceived(withBeep: true, chatId: chatId, delegate: MEGAChatGenericRequestDelegate { [weak self] _, error in - if error.type != .MEGAChatErrorTypeOk { - self?.postNotification(withError: "Error in pushReceived \(error)") - } + MEGAChatSdk.shared.add(self as MEGAChatNotificationDelegate) + MEGAChatSdk.shared.pushReceived(withBeep: true, chatId: chatId, delegate: ChatRequestDelegate { [weak self] result in + guard case let .failure(error) = result else { return } + self?.postNotification(withError: "Error in pushReceived \(error)") }) } private func restartExtensionProcess(with session: String) { NotificationService.session = nil - MEGASdk.sharedNSE.localLogout(with: MEGAGenericRequestDelegate { _, error in - if error.type != .apiOk { - self.postNotification(withError: "SDK error in localLogout \(error)") + MEGASdk.sharedNSE.localLogout(with: RequestDelegate { result in + guard case .success = result else { + if case let .failure(error) = result { + self.postNotification(withError: "SDK error in localLogout \(error)") + } return } - MEGASdkManager.sharedMEGAChatSdk().localLogout(with: MEGAChatGenericRequestDelegate { _, error in - if error.type != .MEGAChatErrorTypeOk { - self.postNotification(withError: "MEGAChat error in localLogout \(error)") + MEGAChatSdk.shared.localLogout(with: ChatRequestDelegate { result in + guard case .success = result else { + if case let .failure(error) = result { + self.postNotification(withError: "MEGAChat error in localLogout \(error)") + } return } if NotificationService.initExtensionProcess(with: session) { @@ -469,33 +476,34 @@ class NotificationService: UNNotificationServiceExtension, MEGAChatNotificationD private static func initChat(with session: String) -> Bool { MEGALogDebug("Init chat") - var chatInit = MEGASdkManager.sharedMEGAChatSdk().initState() + var chatInit = MEGAChatSdk.shared.initState() if chatInit == .notDone { MEGALogDebug("Init state == notDone -> Init Karere Lean Mode") - chatInit = MEGASdkManager.sharedMEGAChatSdk().initKarereLeanMode(withSid: session) + chatInit = MEGAChatSdk.shared.initKarereLeanMode(withSid: session) if chatInit == .error { MEGALogError("Init Karere Lean Mode fails -> logout") - MEGASdkManager.sharedMEGAChatSdk().logout() + MEGAChatSdk.shared.logout() return false } MEGALogDebug("Reset client Id") - MEGASdkManager.sharedMEGAChatSdk().resetClientId() + MEGAChatSdk.shared.resetClientId() } else { MEGALogDebug("Init state != notDone -> Reconnect") MEGAReachabilityManager.shared()?.reconnect() } - MEGASdkManager.sharedMEGAChatSdk().setBackgroundStatus(true) + MEGAChatSdk.shared.setBackgroundStatus(true) return true } private static func loginToMEGA(with session: String) { MEGALogDebug("Login to MEGA") - MEGASdk.sharedNSE.fastLogin(withSession: session, delegate: MEGAGenericRequestDelegate { _, error in - if error.type != .apiOk { - MEGALogError("Login error \(error)") + MEGASdk.sharedNSE.fastLogin(withSession: session, delegate: RequestDelegate { result in + guard case let .failure(error) = result else { return } + + MEGALogError("Login error \(error)") }) } diff --git a/iMEGA/Extensions/MEGANotifications/ScheduleMeetingNotificationInfo.swift b/iMEGA/Extensions/MEGANotifications/ScheduleMeetingNotificationInfo.swift index bc019f3ca0..81a5e167d0 100644 --- a/iMEGA/Extensions/MEGANotifications/ScheduleMeetingNotificationInfo.swift +++ b/iMEGA/Extensions/MEGANotifications/ScheduleMeetingNotificationInfo.swift @@ -1,4 +1,3 @@ - struct ScheduleMeetingNotificationInfo { enum StartTime { case now diff --git a/iMEGA/Extensions/MEGANotifications/UNNotificationContent+Additions.swift b/iMEGA/Extensions/MEGANotifications/UNNotificationContent+Additions.swift index 3be72e33f3..d2ef9f5f71 100644 --- a/iMEGA/Extensions/MEGANotifications/UNNotificationContent+Additions.swift +++ b/iMEGA/Extensions/MEGANotifications/UNNotificationContent+Additions.swift @@ -1,4 +1,3 @@ - extension UNNotificationContent { private enum NotificationType: Int { case startScheduleMeeting = 7 diff --git a/iMEGA/Extensions/MEGAPicker/DocumentActionViewController.swift b/iMEGA/Extensions/MEGAPicker/DocumentActionViewController.swift index dc5cfeff8a..48d29f463b 100644 --- a/iMEGA/Extensions/MEGAPicker/DocumentActionViewController.swift +++ b/iMEGA/Extensions/MEGAPicker/DocumentActionViewController.swift @@ -1,4 +1,5 @@ import FileProviderUI +import MEGAL10n import UIKit final class DocumentActionViewController: FPUIActionExtensionViewController { diff --git a/iMEGA/Extensions/MEGAPickerFileProvider/FileProviderEnumerator.swift b/iMEGA/Extensions/MEGAPickerFileProvider/FileProviderEnumerator.swift index 8991fcc71d..d9294918e8 100644 --- a/iMEGA/Extensions/MEGAPickerFileProvider/FileProviderEnumerator.swift +++ b/iMEGA/Extensions/MEGAPickerFileProvider/FileProviderEnumerator.swift @@ -30,7 +30,7 @@ final class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { let nodeActionUseCase = NodeActionUseCase(repo: NodeActionRepository.newRepo) - try await nodeActionUseCase.fetchnodes() + try await nodeActionUseCase.fetchNodes() } let items = try fetchItems() diff --git a/iMEGA/Extensions/MEGAPickerFileProvider/FileProviderItem.swift b/iMEGA/Extensions/MEGAPickerFileProvider/FileProviderItem.swift index ccbbe0c718..898d679624 100644 --- a/iMEGA/Extensions/MEGAPickerFileProvider/FileProviderItem.swift +++ b/iMEGA/Extensions/MEGAPickerFileProvider/FileProviderItem.swift @@ -3,6 +3,7 @@ import MEGADomain import MEGAPickerFileProviderDomain import MEGAPickerFileProviderRepo import MEGARepo +import MEGASDKRepo import MEGASwift import UniformTypeIdentifiers diff --git a/iMEGA/Extensions/MEGAPickerFileProvider/MEGAPickerFileProvider-Bridging-Header.h b/iMEGA/Extensions/MEGAPickerFileProvider/MEGAPickerFileProvider-Bridging-Header.h index 0fc53a0aae..d3858f0aa8 100644 --- a/iMEGA/Extensions/MEGAPickerFileProvider/MEGAPickerFileProvider-Bridging-Header.h +++ b/iMEGA/Extensions/MEGAPickerFileProvider/MEGAPickerFileProvider-Bridging-Header.h @@ -1,4 +1,3 @@ - #ifndef MEGAPickerFileProvider_Bridging_Header_h #define MEGAPickerFileProvider_Bridging_Header_h diff --git a/iMEGA/Extensions/MEGAShare/CancellableTransfer/ShareExtensionCancellableTransferViewModel.swift b/iMEGA/Extensions/MEGAShare/CancellableTransfer/ShareExtensionCancellableTransferViewModel.swift index b39183089e..a8e3aac5ad 100644 --- a/iMEGA/Extensions/MEGAShare/CancellableTransfer/ShareExtensionCancellableTransferViewModel.swift +++ b/iMEGA/Extensions/MEGAShare/CancellableTransfer/ShareExtensionCancellableTransferViewModel.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation final class ShareExtensionCancellableTransferViewModel: ViewModelType { diff --git a/iMEGA/Extensions/MEGAShare/ExtensionAppearanceManager.swift b/iMEGA/Extensions/MEGAShare/ExtensionAppearanceManager.swift index 1d915200a4..c92a499b50 100644 --- a/iMEGA/Extensions/MEGAShare/ExtensionAppearanceManager.swift +++ b/iMEGA/Extensions/MEGAShare/ExtensionAppearanceManager.swift @@ -1,4 +1,3 @@ - import Foundation class ExtensionAppearanceManager: NSObject { diff --git a/iMEGA/Extensions/MEGAShare/Model/ShareAttachment.h b/iMEGA/Extensions/MEGAShare/Model/ShareAttachment.h index 0e4e4bfc9c..7be745b631 100644 --- a/iMEGA/Extensions/MEGAShare/Model/ShareAttachment.h +++ b/iMEGA/Extensions/MEGAShare/Model/ShareAttachment.h @@ -1,4 +1,3 @@ - #import #import "ShareAttachmentType.h" diff --git a/iMEGA/Extensions/MEGAShare/Model/ShareAttachment.m b/iMEGA/Extensions/MEGAShare/Model/ShareAttachment.m index 40639ab690..82bd627cc6 100644 --- a/iMEGA/Extensions/MEGAShare/Model/ShareAttachment.m +++ b/iMEGA/Extensions/MEGAShare/Model/ShareAttachment.m @@ -1,4 +1,3 @@ - #import "ShareAttachment.h" #import diff --git a/iMEGA/Extensions/MEGAShare/OpenAppRequiredViewController.h b/iMEGA/Extensions/MEGAShare/OpenAppRequiredViewController.h index a18a60e73b..2cfbdb9061 100644 --- a/iMEGA/Extensions/MEGAShare/OpenAppRequiredViewController.h +++ b/iMEGA/Extensions/MEGAShare/OpenAppRequiredViewController.h @@ -1,4 +1,3 @@ - #import @interface OpenAppRequiredViewController : UIViewController diff --git a/iMEGA/Extensions/MEGAShare/OpenAppRequiredViewController.m b/iMEGA/Extensions/MEGAShare/OpenAppRequiredViewController.m index c6f8aacece..e053401997 100644 --- a/iMEGA/Extensions/MEGAShare/OpenAppRequiredViewController.m +++ b/iMEGA/Extensions/MEGAShare/OpenAppRequiredViewController.m @@ -1,9 +1,10 @@ - #import "OpenAppRequiredViewController.h" #import "UIViewController+MNZCategory.h" #import "MEGAShare-Swift.h" +@import MEGAL10nObjc; + @interface OpenAppRequiredViewController () @property (weak, nonatomic) IBOutlet UILabel *descriptionLabel; @@ -19,12 +20,12 @@ - (void)viewDidLoad { [super viewDidLoad]; if (self.isLoginRequired) { - self.descriptionLabel.text = NSLocalizedString(@"openMEGAAndSignInToContinue", @"Text shown when you try to use a MEGA extension in iOS and you aren't logged"); + self.descriptionLabel.text = LocalizedString(@"openMEGAAndSignInToContinue", @"Text shown when you try to use a MEGA extension in iOS and you aren't logged"); } else { - self.descriptionLabel.text = NSLocalizedString(@"extensions.OpenApp.Message", nil); + self.descriptionLabel.text = LocalizedString(@"extensions.OpenApp.Message", @""); } - [self.openButton setTitle:NSLocalizedString(@"openButton", @"Button title to trigger the action of opening the file without downloading or opening it.") forState:UIControlStateNormal]; + [self.openButton setTitle:LocalizedString(@"openButton", @"Button title to trigger the action of opening the file without downloading or opening it.") forState:UIControlStateNormal]; } - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { diff --git a/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController+TableViewDataSource.swift b/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController+TableViewDataSource.swift index d68c684f25..08daef37c0 100644 --- a/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController+TableViewDataSource.swift +++ b/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController+TableViewDataSource.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGASwiftUI enum ShareDestinationSection: Int, CaseIterable { diff --git a/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController.h b/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController.h index c31f64c65f..2581e69793 100644 --- a/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController.h +++ b/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController.h @@ -1,4 +1,3 @@ - #import @interface ShareDestinationTableViewController : UITableViewController diff --git a/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController.m b/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController.m index afd57f233e..56b68202db 100644 --- a/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController.m +++ b/iMEGA/Extensions/MEGAShare/ShareDestinationTableViewController.m @@ -1,4 +1,3 @@ - #import "ShareDestinationTableViewController.h" #import "BrowserViewController.h" @@ -9,6 +8,8 @@ #import "ShareViewController.h" #import "UIImageView+MNZCategory.h" +@import MEGAL10nObjc; + @interface ShareDestinationTableViewController () @property (weak, nonatomic) UINavigationController *navigationController; @@ -30,7 +31,7 @@ - (void)viewDidLoad { self.shareViewController = (ShareViewController *)self.navigationController.parentViewController; self.sharedUserDefaults = [NSUserDefaults.alloc initWithSuiteName:MEGAGroupIdentifier]; - self.cancelBarButtonItem.title = NSLocalizedString(@"cancel", nil); + self.cancelBarButtonItem.title = LocalizedString(@"cancel", @""); // Add observers to get notified when the extension goes to background and comes back to foreground: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive) diff --git a/iMEGA/Extensions/MEGAShare/ShareViewController+Additions.swift b/iMEGA/Extensions/MEGAShare/ShareViewController+Additions.swift index 1b74c71205..eb310838a8 100644 --- a/iMEGA/Extensions/MEGAShare/ShareViewController+Additions.swift +++ b/iMEGA/Extensions/MEGAShare/ShareViewController+Additions.swift @@ -1,3 +1,4 @@ +import MEGAL10n extension ShareViewController { @objc func successSendToChatMessage(attachments: [ShareAttachment], receiverCount: Int) -> String { diff --git a/iMEGA/Extensions/MEGAShare/ShareViewController+OpenAppRequiredView.swift b/iMEGA/Extensions/MEGAShare/ShareViewController+OpenAppRequiredView.swift index 3c571c1b4b..097747f533 100644 --- a/iMEGA/Extensions/MEGAShare/ShareViewController+OpenAppRequiredView.swift +++ b/iMEGA/Extensions/MEGAShare/ShareViewController+OpenAppRequiredView.swift @@ -1,4 +1,6 @@ import CoreGraphics +import MEGAL10n + extension ShareViewController { @objc func addOpenAppView() { guard let openAppNC else { diff --git a/iMEGA/Extensions/MEGAShare/ShareViewController.h b/iMEGA/Extensions/MEGAShare/ShareViewController.h index f673214370..19642d0dbf 100644 --- a/iMEGA/Extensions/MEGAShare/ShareViewController.h +++ b/iMEGA/Extensions/MEGAShare/ShareViewController.h @@ -1,4 +1,3 @@ - #import "BrowserViewController.h" #import "SendToViewController.h" #import "OpenAppRequiredViewController.h" diff --git a/iMEGA/Extensions/MEGAShare/ShareViewController.m b/iMEGA/Extensions/MEGAShare/ShareViewController.m index 878e56092a..643989cbac 100644 --- a/iMEGA/Extensions/MEGAShare/ShareViewController.m +++ b/iMEGA/Extensions/MEGAShare/ShareViewController.m @@ -1,4 +1,3 @@ - #import "ShareViewController.h" #import @@ -28,6 +27,7 @@ @import Firebase; @import MEGASDKRepo; +@import MEGAL10nObjc; #define MNZ_ANIMATION_TIME 0.35 @@ -290,7 +290,7 @@ - (void)initChatAndStartLogging { } - (void)loginToMEGA { - self.navigationItem.title = NSLocalizedString(@"MEGA", nil); + self.navigationItem.title = LocalizedString(@"MEGA", @""); LaunchViewController *launchVC = [[UIStoryboard storyboardWithName:@"Launch" bundle:[NSBundle bundleForClass:[LaunchViewController class]]] instantiateViewControllerWithIdentifier:@"LaunchViewControllerID"]; launchVC.view.frame = CGRectMake(0.0f, 0.0f, self.view.frame.size.width, self.view.frame.size.height); @@ -341,7 +341,7 @@ - (void)presentPasscode { [passcodeVC showLockScreenOver:self.view.superview withAnimation:YES withLogout:YES - andLogoutTitle:NSLocalizedString(@"logoutLabel", nil)]; + andLogoutTitle:LocalizedString(@"logoutLabel", @"")]; [passcodeVC.view setFrame:CGRectMake(0.0f, 0.0f, self.view.frame.size.width, self.view.frame.size.height)]; passcodeVC.modalPresentationStyle = UIModalPresentationFullScreen; @@ -437,8 +437,8 @@ - (MEGAGenericRequestDelegate *)logoutDelegate { [Helper logout]; [[MEGASdkManager sharedMEGASdk] mnz_setAccountDetails:nil]; if (sessionInvalidateInOtherClient) { - UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"loggedOut_alertTitle", nil) message:NSLocalizedString(@"loggedOutFromAnotherLocation", nil) preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alert = [UIAlertController alertControllerWithTitle:LocalizedString(@"loggedOut_alertTitle", @"") message:LocalizedString(@"loggedOutFromAnotherLocation", @"") preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { [weakSelf dismissViewControllerAnimated:YES completion:^{ [weakSelf.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; }]; @@ -846,9 +846,9 @@ - (void)alertIfNeededAndDismiss { } if (self.unsupportedAssets > 0) { - NSString *message = NSLocalizedString(@"shareExtensionUnsupportedAssets", @"Inform user that there were unsupported assets in the share extension."); + NSString *message = LocalizedString(@"shareExtensionUnsupportedAssets", @"Inform user that there were unsupported assets in the share extension."); UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { if (self.pendingAssets == self.unsupportedAssets) { [self hideViewWithCompletion:^{ [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; @@ -883,7 +883,7 @@ - (void)processTransfers { } - (void)logout { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudLogOut"] status:NSLocalizedString(@"loggingOut", @"String shown when you are logging out of your account.")]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudLogOut"] status:LocalizedString(@"loggingOut", @"String shown when you are logging out of your account.")]; [[MEGASdkManager sharedMEGASdk] logout]; } @@ -930,7 +930,7 @@ - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { case MEGARequestTypeLogout: { if (request.paramType != MEGAErrorTypeApiESSL) { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudLogOut"] status:NSLocalizedString(@"loggingOut", @"String shown when you are logging out of your account.")]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudLogOut"] status:LocalizedString(@"loggingOut", @"String shown when you are logging out of your account.")]; } break; } @@ -988,7 +988,7 @@ - (void)onTransferUpdate:(MEGASdk *)api transfer:(MEGATransfer *)transfer { - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error { if (error.type) { [self oneUnsupportedMore]; - MEGALogError(@"Transfer finished with error: %@", NSLocalizedString(error.name, nil)); + MEGALogError(@"Transfer finished with error: %@", LocalizedString(error.name, @"")); return; } diff --git a/iMEGA/Extensions/MEGAShare/Supporting Files/MEGAShare-Bridging-Header.h b/iMEGA/Extensions/MEGAShare/Supporting Files/MEGAShare-Bridging-Header.h index 2aee2bb02b..7c1f548bc8 100644 --- a/iMEGA/Extensions/MEGAShare/Supporting Files/MEGAShare-Bridging-Header.h +++ b/iMEGA/Extensions/MEGAShare/Supporting Files/MEGAShare-Bridging-Header.h @@ -1,4 +1,3 @@ - #ifndef MEGAShare_Bridging_Header_h #define MEGAShare_Bridging_Header_h diff --git a/iMEGA/Extensions/MEGAShare/Supporting Files/MEGAShare-PrefixHeader.pch b/iMEGA/Extensions/MEGAShare/Supporting Files/MEGAShare-PrefixHeader.pch index d5f525b803..0d11f94e5c 100644 --- a/iMEGA/Extensions/MEGAShare/Supporting Files/MEGAShare-PrefixHeader.pch +++ b/iMEGA/Extensions/MEGAShare/Supporting Files/MEGAShare-PrefixHeader.pch @@ -1,4 +1,3 @@ - #ifdef __OBJC__ #import "MEGAConstants.h" #import "MEGALogMacros.h" diff --git a/iMEGA/Extensions/MEGAWidget/Assets.xcassets/Colors/#00A886.colorset/Contents.json b/iMEGA/Extensions/MEGAWidget/Assets.xcassets/Colors/#00A886.colorset/Contents.json deleted file mode 100644 index f59dc5d05d..0000000000 --- a/iMEGA/Extensions/MEGAWidget/Assets.xcassets/Colors/#00A886.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0x86", - "green" : "0xA8", - "red" : "0x00" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iMEGA/Extensions/MEGAWidget/MEGAWidget.swift b/iMEGA/Extensions/MEGAWidget/MEGAWidget.swift index 8e532c01bc..d82f584481 100644 --- a/iMEGA/Extensions/MEGAWidget/MEGAWidget.swift +++ b/iMEGA/Extensions/MEGAWidget/MEGAWidget.swift @@ -1,4 +1,3 @@ - import Firebase import SwiftUI import WidgetKit diff --git a/iMEGA/Extensions/MEGAWidget/QuickAccess/Favourites/FavouritesQuickAccessWidget.swift b/iMEGA/Extensions/MEGAWidget/QuickAccess/Favourites/FavouritesQuickAccessWidget.swift index 7713f78631..bc5dc4ba07 100644 --- a/iMEGA/Extensions/MEGAWidget/QuickAccess/Favourites/FavouritesQuickAccessWidget.swift +++ b/iMEGA/Extensions/MEGAWidget/QuickAccess/Favourites/FavouritesQuickAccessWidget.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo import SwiftUI import WidgetKit diff --git a/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/QuickAccessItemModel.swift b/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/QuickAccessItemModel.swift index 43d0191b6a..befe9830a7 100644 --- a/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/QuickAccessItemModel.swift +++ b/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/QuickAccessItemModel.swift @@ -1,4 +1,3 @@ - import SwiftUI struct QuickAccessItemModel { diff --git a/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/QuickAccessWidgetEntry.swift b/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/QuickAccessWidgetEntry.swift index 03502ee7a4..bb6cd2cd50 100644 --- a/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/QuickAccessWidgetEntry.swift +++ b/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/QuickAccessWidgetEntry.swift @@ -1,4 +1,3 @@ - import SwiftUI import WidgetKit diff --git a/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/SectionDetail.swift b/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/SectionDetail.swift index b46250f826..46f545ea60 100644 --- a/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/SectionDetail.swift +++ b/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/SectionDetail.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n struct SectionDetail: Hashable { let title: String diff --git a/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/WidgetStatus.swift b/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/WidgetStatus.swift index 82568ca84b..5593b38f78 100644 --- a/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/WidgetStatus.swift +++ b/iMEGA/Extensions/MEGAWidget/QuickAccess/Model/WidgetStatus.swift @@ -1,4 +1,3 @@ - enum WidgetStatus { case notConnected case noSession diff --git a/iMEGA/Extensions/MEGAWidget/QuickAccess/Offline/OfflineQuickAccessWidget.swift b/iMEGA/Extensions/MEGAWidget/QuickAccess/Offline/OfflineQuickAccessWidget.swift index 91cd74bd20..b1388a7384 100644 --- a/iMEGA/Extensions/MEGAWidget/QuickAccess/Offline/OfflineQuickAccessWidget.swift +++ b/iMEGA/Extensions/MEGAWidget/QuickAccess/Offline/OfflineQuickAccessWidget.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo import SwiftUI import WidgetKit diff --git a/iMEGA/Extensions/MEGAWidget/QuickAccess/Recents/RecentsQuickAccessWidget.swift b/iMEGA/Extensions/MEGAWidget/QuickAccess/Recents/RecentsQuickAccessWidget.swift index 7ba4183107..e7d5a9328a 100644 --- a/iMEGA/Extensions/MEGAWidget/QuickAccess/Recents/RecentsQuickAccessWidget.swift +++ b/iMEGA/Extensions/MEGAWidget/QuickAccess/Recents/RecentsQuickAccessWidget.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo import SwiftUI import WidgetKit diff --git a/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/DetailItemView.swift b/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/DetailItemView.swift index 460a02b34b..6f417b0364 100644 --- a/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/DetailItemView.swift +++ b/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/DetailItemView.swift @@ -1,4 +1,3 @@ - import SwiftUI struct DetailItemView: View { diff --git a/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/GridView.swift b/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/GridView.swift index 5b65343d44..f748b4be00 100644 --- a/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/GridView.swift +++ b/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/GridView.swift @@ -1,4 +1,3 @@ - import SwiftUI struct GridView: View { diff --git a/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/QuickAccessWidgetView.swift b/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/QuickAccessWidgetView.swift index a2acdf9fb0..a2baedfec1 100644 --- a/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/QuickAccessWidgetView.swift +++ b/iMEGA/Extensions/MEGAWidget/QuickAccess/Views/QuickAccessWidgetView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import SwiftUI struct QuickAccessWidgetView: View { @@ -98,7 +98,7 @@ struct QuickAccessWidgetView: View { Spacer() Text(Strings.Localizable.login) .font(.system(size: 17, weight: .semibold, design: .default)) - .foregroundColor(Color("#00A886")) + .foregroundColor(Color("00A886")) Spacer() } .frame(maxWidth: .infinity, minHeight: 44) diff --git a/iMEGA/Extensions/MEGAWidget/Shortcuts/ShortcutDetail.swift b/iMEGA/Extensions/MEGAWidget/Shortcuts/ShortcutDetail.swift index 74d3d991e1..7a080fb028 100644 --- a/iMEGA/Extensions/MEGAWidget/Shortcuts/ShortcutDetail.swift +++ b/iMEGA/Extensions/MEGAWidget/Shortcuts/ShortcutDetail.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n import SwiftUI struct ShortcutDetail: Hashable { @@ -8,7 +9,7 @@ struct ShortcutDetail: Hashable { let bottomBackgroundColor: Color let link: String - static let uploadFile = ShortcutDetail(title: Strings.Localizable.uploadFile, imageName: "uploadFileWidgetXL", topBackgroundColor: Color("#00A886"), bottomBackgroundColor: Color("#008A6E"), link: "mega://widget.shortcut.uploadFile") + static let uploadFile = ShortcutDetail(title: Strings.Localizable.uploadFile, imageName: "uploadFileWidgetXL", topBackgroundColor: Color("00A886"), bottomBackgroundColor: Color("#008A6E"), link: "mega://widget.shortcut.uploadFile") static let scanDocument = ShortcutDetail(title: Strings.Localizable.scanDocument, imageName: "scanDocumentWidgetXL", topBackgroundColor: Color("#F9B35F"), bottomBackgroundColor: Color("#E68F4D"), link: "mega://widget.shortcut.scanDocument") diff --git a/iMEGA/Extensions/MEGAWidget/Shortcuts/ShortcutsWidget.swift b/iMEGA/Extensions/MEGAWidget/Shortcuts/ShortcutsWidget.swift index d193dc51dd..53e3a647d2 100644 --- a/iMEGA/Extensions/MEGAWidget/Shortcuts/ShortcutsWidget.swift +++ b/iMEGA/Extensions/MEGAWidget/Shortcuts/ShortcutsWidget.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import MEGASwift import SwiftUI import WidgetKit diff --git a/iMEGA/Extensions/MEGAWidget/Supporting Files/MEGAWidget-Bridging-Header.h b/iMEGA/Extensions/MEGAWidget/Supporting Files/MEGAWidget-Bridging-Header.h index c32470140b..c5f821e40e 100644 --- a/iMEGA/Extensions/MEGAWidget/Supporting Files/MEGAWidget-Bridging-Header.h +++ b/iMEGA/Extensions/MEGAWidget/Supporting Files/MEGAWidget-Bridging-Header.h @@ -1,4 +1,3 @@ - #ifndef MEGAWidget_Bridging_Header_h #define MEGAWidget_Bridging_Header_h diff --git a/iMEGA/GroupChatDetailsViewController.swift b/iMEGA/GroupChatDetailsViewController.swift index a529430b84..50c52d1f7a 100644 --- a/iMEGA/GroupChatDetailsViewController.swift +++ b/iMEGA/GroupChatDetailsViewController.swift @@ -1,23 +1,24 @@ import Foundation import MEGADomain +import MEGAL10n import MEGASDKRepo extension GroupChatDetailsViewController { @objc func addChatCallDelegate() { - MEGASdkManager.sharedMEGAChatSdk().add(self as (any MEGAChatCallDelegate)) + MEGAChatSdk.shared.add(self as (any MEGAChatCallDelegate)) } @objc func removeChatCallDelegate() { - MEGASdkManager.sharedMEGAChatSdk().remove(self as (any MEGAChatCallDelegate)) + MEGAChatSdk.shared.remove(self as (any MEGAChatCallDelegate)) } @objc func addChatRoomDelegate() { - MEGASdkManager.sharedMEGAChatSdk().addChatRoomDelegate(chatRoom.chatId, delegate: self) + MEGAChatSdk.shared.addChatRoomDelegate(chatRoom.chatId, delegate: self) } @objc func removeChatRoomDelegate() { - MEGASdkManager.sharedMEGAChatSdk().removeChatRoomDelegate(chatRoom.chatId, delegate: self) + MEGAChatSdk.shared.removeChatRoomDelegate(chatRoom.chatId, delegate: self) } @objc func shouldShowAddParticipants() -> Bool { @@ -44,14 +45,14 @@ extension GroupChatDetailsViewController { self?.endCallDialog = nil } endCallAction: { [weak self] in guard let self = self, - let call = MEGASdkManager.sharedMEGAChatSdk().chatCall(forChatId: self.chatRoom.chatId) else { + let call = MEGAChatSdk.shared.chatCall(forChatId: self.chatRoom.chatId) else { return } - let statsRepoSitory = AnalyticsRepository(sdk: MEGASdkManager.sharedMEGASdk()) + let statsRepoSitory = AnalyticsRepository(sdk: MEGASdk.shared) AnalyticsEventUseCase(repository: statsRepoSitory).sendAnalyticsEvent(.meetings(.endCallForAll)) - MEGASdkManager.sharedMEGAChatSdk().endChatCall(call.callId) + MEGAChatSdk.shared.endChatCall(call.callId) self.navigationController?.popViewController(animated: true) } @@ -101,7 +102,7 @@ extension GroupChatDetailsViewController { ) { [weak self] handles in guard let self = self else { return } for handle in handles { - MEGASdkManager.sharedMEGAChatSdk().invite( + MEGAChatSdk.shared.invite( toChat: self.chatRoom.chatId, user: handle, privilege: MEGAChatRoomPrivilege.standard.rawValue @@ -152,7 +153,7 @@ extension GroupChatDetailsViewController: GroupChatDetailsViewTableViewCellDeleg case GroupChatDetailsSection.chatNotifications.rawValue: changeChatNotificationStatus(sender: sender) case GroupChatDetailsSection.allowNonHostToAddParticipants.rawValue: - MEGASdkManager.sharedMEGAChatSdk().openInvite(sender.isOn, chatId: chatRoom.chatId) + MEGAChatSdk.shared.openInvite(sender.isOn, chatId: chatRoom.chatId) default: break } diff --git a/iMEGA/Home/Recents/RecentsViewController.h b/iMEGA/Home/Recents/RecentsViewController.h index 16156c2ad4..741c0b1f27 100644 --- a/iMEGA/Home/Recents/RecentsViewController.h +++ b/iMEGA/Home/Recents/RecentsViewController.h @@ -1,4 +1,3 @@ - #import @class CloudDriveViewController; diff --git a/iMEGA/Home/Recents/RecentsViewController.m b/iMEGA/Home/Recents/RecentsViewController.m index 13505cb44e..da569a7e91 100644 --- a/iMEGA/Home/Recents/RecentsViewController.m +++ b/iMEGA/Home/Recents/RecentsViewController.m @@ -1,4 +1,3 @@ - #import "RecentsViewController.h" #import "UIScrollView+EmptyDataSet.h" @@ -20,6 +19,7 @@ #import "NSArray+MNZCategory.h" @import MEGAFoundation; +@import MEGAL10nObjc; @import MEGASDKRepo; static const NSTimeInterval RecentsViewReloadTimeDelay = 3.0; @@ -186,9 +186,9 @@ - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger } if (recentActionBucket.timestamp.isToday) { - recentsTVHFV.dateLabel.text = NSLocalizedString(@"Today", @""); + recentsTVHFV.dateLabel.text = LocalizedString(@"Today", @""); } else if (recentActionBucket.timestamp.isYesterday) { - recentsTVHFV.dateLabel.text = NSLocalizedString(@"Yesterday", @""); + recentsTVHFV.dateLabel.text = LocalizedString(@"Yesterday", @""); } else { NSString *dateString = [self.dateFormatter stringFromDate:recentActionBucket.timestamp]; recentsTVHFV.dateLabel.text = dateString; @@ -272,8 +272,8 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath - (UIViewController *)viewControllerForNode:(MEGANode *)node withFolderLink:(BOOL)isFolderLink { if ([FileExtensionGroupOCWrapper verifyIsMultiMedia:node.name] && MEGASdkManager.sharedMEGAChatSdk.mnz_existsActiveCall) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:NSLocalizedString(@"It is not possible to play content while there is a call in progress", @"Message shown when there is an ongoing call and the user tries to play an audio or video") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:LocalizedString(@"It is not possible to play content while there is a call in progress", @"Message shown when there is an ongoing call and the user tries to play an audio or video") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; return alertController; } else { return [node mnz_viewControllerForNodeInFolderLink:isFolderLink fileLink:nil inViewController: self]; @@ -286,7 +286,7 @@ - (nullable UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView { EmptyStateView *emptyStateView = [EmptyStateView.alloc initForHomeWithImage:[self imageForEmptyState] title:[self titleForEmptyState] description:[self descriptionForEmptyState] buttonTitle:[self buttonTitleForEmptyState]]; [emptyStateView.descriptionButton addTarget:self action:@selector(buttonTouchUpInsideEmptyState) forControlEvents:UIControlEventTouchUpInside]; - [emptyStateView.descriptionButton setTitle:NSLocalizedString(@"recents.emptyState.activityHidden.button", @"Title of the button show in Recents on the empty state when the recent activity is hidden") forState:UIControlStateNormal]; + [emptyStateView.descriptionButton setTitle:LocalizedString(@"recents.emptyState.activityHidden.button", @"Title of the button show in Recents on the empty state when the recent activity is hidden") forState:UIControlStateNormal]; emptyStateView.descriptionButton.hidden = RecentsPreferenceManager.showRecents; return emptyStateView; @@ -297,9 +297,9 @@ - (nullable UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView { - (NSString *)titleForEmptyState { NSString *text; if (RecentsPreferenceManager.showRecents) { - text = (MEGAReachabilityManager.isReachable) ? NSLocalizedString(@"No recent activity", @"Message shown when the user has not recent activity in their account.") : NSLocalizedString(@"noInternetConnection", @"Text shown on the app when you don't have connection to the internet or when you have lost it"); + text = (MEGAReachabilityManager.isReachable) ? LocalizedString(@"No recent activity", @"Message shown when the user has not recent activity in their account.") : LocalizedString(@"noInternetConnection", @"Text shown on the app when you don't have connection to the internet or when you have lost it"); } else { - text = NSLocalizedString(@"recents.emptyState.activityHidden.title", @"Title show in Recents on the empty state when the recent activity is hidden"); + text = LocalizedString(@"recents.emptyState.activityHidden.title", @"Title show in Recents on the empty state when the recent activity is hidden"); } return text; @@ -309,7 +309,7 @@ - (NSString *)descriptionForEmptyState { NSString *text = @""; if (RecentsPreferenceManager.showRecents) { if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); + text = LocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); } } @@ -330,7 +330,7 @@ - (UIImage *)imageForEmptyState { - (NSString *)buttonTitleForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); + text = LocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); } return text; diff --git a/iMEGA/Home/Repository/SDK/Banner/BannerRepository.swift b/iMEGA/Home/Repository/SDK/Banner/BannerRepository.swift deleted file mode 100644 index 4ded6b1edc..0000000000 --- a/iMEGA/Home/Repository/SDK/Banner/BannerRepository.swift +++ /dev/null @@ -1,56 +0,0 @@ -protocol BannerRepositoryProtocol { - - func banners( - completion: @escaping (Result<[BannerEntity], BannerErrorEntity>) -> Void - ) - - func dismissBanner( - withBannerId bannerId: Int, - completion: ((Result) -> Void)? - ) -} - -struct BannerRepository: BannerRepositoryProtocol { - - let sdk: MEGASdk - - // MARK: - BannerRepositoryProtocol - - func banners(completion: @escaping (Result<[BannerEntity], BannerErrorEntity>) -> Void) { - - func mapError(sdkError: MEGASDKErrorType) -> BannerErrorEntity { - switch sdkError { - case .accessDenied: return .userSessionTimeout - case .internalError: return .internal - case .resourceNotExists: return .resourceDoesNotExist - default: return .unexpected - } - } - - func mapValue(request: MEGARequest) -> [BannerEntity] { - request.bannerList.bannerEntities - } - - let sdkDelegate = MEGAResultRequestDelegate { (result) in - switch result { - case .failure(let errorType): - completion(.failure(mapError(sdkError: errorType))) - case .success(let request): - completion(.success(request.bannerList.bannerEntities)) - } - } - sdk.getBanners(sdkDelegate) - } - - func dismissBanner( - withBannerId bannerId: Int, - completion: ((Result) -> Void)? - ) { - let sdkDelegate = MEGAResultMappingRequestDelegate( - completion: completion ?? { _ in }, - mapValue: { _ in return () }, - mapError: { _ in return .unexpected } - ) - sdk.dismissBanner(bannerId, delegate: sdkDelegate) - } -} diff --git a/iMEGA/Home/Repository/SDK/SDKNodeClipboardOperationRepository.swift b/iMEGA/Home/Repository/SDK/SDKNodeClipboardOperationRepository.swift index 13e6448e34..a5f375aacb 100644 --- a/iMEGA/Home/Repository/SDK/SDKNodeClipboardOperationRepository.swift +++ b/iMEGA/Home/Repository/SDK/SDKNodeClipboardOperationRepository.swift @@ -1,4 +1,3 @@ - class SDKNodeClipboardOperationRepository: NSObject, MEGARequestDelegate { enum ClipboardOperation { case move diff --git a/iMEGA/Home/Repository/SDK/SDKTransferListenerRepository.swift b/iMEGA/Home/Repository/SDK/SDKTransferListenerRepository.swift index 2c0b91ec31..0be85fc008 100644 --- a/iMEGA/Home/Repository/SDK/SDKTransferListenerRepository.swift +++ b/iMEGA/Home/Repository/SDK/SDKTransferListenerRepository.swift @@ -1,4 +1,3 @@ - final class SDKTransferListenerRepository: NSObject, MEGATransferDelegate { private let sdk: MEGASdk diff --git a/iMEGA/Home/Scenes/FavouritesScene/FavouritesRouter.swift b/iMEGA/Home/Scenes/FavouritesScene/FavouritesRouter.swift index 7581203d23..631733f500 100644 --- a/iMEGA/Home/Scenes/FavouritesScene/FavouritesRouter.swift +++ b/iMEGA/Home/Scenes/FavouritesScene/FavouritesRouter.swift @@ -1,5 +1,6 @@ import MEGADomain import MEGAPresentation +import MEGASDKRepo final class FavouritesRouter: NSObject, Routing { private weak var navigationController: UINavigationController? @@ -18,7 +19,7 @@ extension FavouritesRouter: FavouritesRouting { let favouritesVC = UIStoryboard(name: "Favourites", bundle: nil) .instantiateViewController(withIdentifier: "FavouritesViewControllerID") as! FavouritesViewController - let repository = FavouriteNodesRepository(sdk: MEGASdkManager.sharedMEGASdk()) + let repository = FavouriteNodesRepository.newRepo let favouritesUseCase = FavouriteNodesUseCase(repo: repository) let viewModel = FavouritesViewModel(router: self, favouritesUseCase: favouritesUseCase) diff --git a/iMEGA/Home/Scenes/Home/View/HomeViewController.swift b/iMEGA/Home/Scenes/Home/View/HomeViewController.swift index 54d8eaee8c..6311812d32 100644 --- a/iMEGA/Home/Scenes/Home/View/HomeViewController.swift +++ b/iMEGA/Home/Scenes/Home/View/HomeViewController.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation import MEGAUIKit import UIKit @@ -96,7 +97,8 @@ final class HomeViewController: UIViewController { return offlineVC }() - var searchResultViewController: HomeSearchResultViewController! + var searchResultViewController: UIViewController! + var searchResultsBridge: SearchResultsBridge! // MARK: - ViewController Lifecycles @@ -291,8 +293,10 @@ final class HomeViewController: UIViewController { private func setupSearchBarView(_ searchBarView: MEGASearchBarView) { searchBarView.delegate = self - searchBarView.edittingDelegate = searchResultViewController - searchResultViewController.searchHintSelectDelegate = searchBarView + searchBarView.editingDelegate = searchResultsBridge + searchResultsBridge.didSelectTextTrampoline = {[weak searchBarView] text in + searchBarView?.didSelect(searchText: text) + } } private func setupBannerCollection() { @@ -655,6 +659,7 @@ extension HomeViewController: MEGASearchBarViewDelegate { searchResultViewController?.removeFromParent() searchResultContainerView?.removeFromSuperview() searchResultContainerView = nil + searchResultsBridge.didFinishSearching() } } diff --git a/iMEGA/Home/Scenes/Home/View/Pannel/SlidePanelView.swift b/iMEGA/Home/Scenes/Home/View/Pannel/SlidePanelView.swift index 80301bf77f..5538ff1a79 100644 --- a/iMEGA/Home/Scenes/Home/View/Pannel/SlidePanelView.swift +++ b/iMEGA/Home/Scenes/Home/View/Pannel/SlidePanelView.swift @@ -1,4 +1,5 @@ import Combine +import MEGAL10n import UIKit protocol SlidePanelDelegate: AnyObject { diff --git a/iMEGA/Home/Scenes/Home/ViewModel/BannerViewModel.swift b/iMEGA/Home/Scenes/Home/ViewModel/BannerViewModel.swift index d7a9c02679..bad0b1fb36 100644 --- a/iMEGA/Home/Scenes/Home/ViewModel/BannerViewModel.swift +++ b/iMEGA/Home/Scenes/Home/ViewModel/BannerViewModel.swift @@ -1,4 +1,5 @@ import Foundation +import MEGADomain protocol HomeBannerViewModelInputs { diff --git a/iMEGA/Home/Scenes/Home/ViewModel/MyAvatarViewModel.swift b/iMEGA/Home/Scenes/Home/ViewModel/MyAvatarViewModel.swift index 211ac4b62c..ee0f48cd29 100644 --- a/iMEGA/Home/Scenes/Home/ViewModel/MyAvatarViewModel.swift +++ b/iMEGA/Home/Scenes/Home/ViewModel/MyAvatarViewModel.swift @@ -34,7 +34,7 @@ final class MyAvatarViewModel: NSObject { // MARK: - View States - var avatarImage: Result? + var avatarImage: Result? var userAlertCount: Int = 0 diff --git a/iMEGA/Home/Scenes/Home/ViewModel/RecentActionViewModel.swift b/iMEGA/Home/Scenes/Home/ViewModel/RecentActionViewModel.swift index d2715a01ac..bb1272f55d 100644 --- a/iMEGA/Home/Scenes/Home/ViewModel/RecentActionViewModel.swift +++ b/iMEGA/Home/Scenes/Home/ViewModel/RecentActionViewModel.swift @@ -66,12 +66,10 @@ final class HomeRecentActionViewModel: if node.isFavourite { Task { try await nodeFavouriteActionUseCase.unFavourite(node: node.toNodeEntity()) - QuickAccessWidgetManager().deleteFavouriteItem(for: node) } } else { Task { try await nodeFavouriteActionUseCase.favourite(node: node.toNodeEntity()) - QuickAccessWidgetManager().insertFavouriteItem(for: node) } } } diff --git a/iMEGA/Home/Scenes/HomeExplore/View/ExplorerBaseViewController.swift b/iMEGA/Home/Scenes/HomeExplore/View/ExplorerBaseViewController.swift index 2ba103eed6..0a4ac961e1 100644 --- a/iMEGA/Home/Scenes/HomeExplore/View/ExplorerBaseViewController.swift +++ b/iMEGA/Home/Scenes/HomeExplore/View/ExplorerBaseViewController.swift @@ -84,7 +84,7 @@ class ExplorerBaseViewController: UIViewController { return } - let favoriteUseCase = NodeFavouriteActionUseCase(nodeFavouriteRepository: NodeFavouriteActionRepository(sdk: .shared)) + let favoriteUseCase = NodeFavouriteActionUseCase(nodeFavouriteRepository: NodeFavouriteActionRepository.newRepo) selectedNodes.forEach { node in if node.isFavourite { diff --git a/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerGridViewState.swift b/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerGridViewState.swift index 4e69ee4534..101caf85e3 100644 --- a/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerGridViewState.swift +++ b/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerGridViewState.swift @@ -1,4 +1,3 @@ - class FilesExplorerContainerGridViewState: FilesExplorerContainerViewState { override func showContent() { super.showContent() diff --git a/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerListViewState.swift b/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerListViewState.swift index d6ef8a277b..da1eaadf45 100644 --- a/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerListViewState.swift +++ b/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerListViewState.swift @@ -1,4 +1,3 @@ - class FilesExplorerContainerListViewState: FilesExplorerContainerViewState { override func showContent() { super.showContent() diff --git a/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerViewState.swift b/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerViewState.swift index 0c2876075e..1dec610fa5 100644 --- a/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerViewState.swift +++ b/iMEGA/Home/Scenes/HomeExplore/View/ExplorerContainerState/FilesExplorerContainerViewState.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n class FilesExplorerContainerViewState: FilesExplorerViewControllerDelegate { class var identifier: AnyHashable { @@ -146,10 +147,8 @@ class FilesExplorerContainerViewState: FilesExplorerViewControllerDelegate { switch count { case 0: title = Strings.Localizable.selectTitle - case 1: - title = Strings.Localizable.oneItemSelected(count) default: - title = Strings.Localizable.itemsSelected(count) + title = Strings.Localizable.General.Format.itemsSelected(count) } containerViewController.updateTitle(title) diff --git a/iMEGA/Home/Scenes/HomeExplore/View/ExplorerToolbarConfigurator.swift b/iMEGA/Home/Scenes/HomeExplore/View/ExplorerToolbarConfigurator.swift index 0206f01302..b7b0de22c4 100644 --- a/iMEGA/Home/Scenes/HomeExplore/View/ExplorerToolbarConfigurator.swift +++ b/iMEGA/Home/Scenes/HomeExplore/View/ExplorerToolbarConfigurator.swift @@ -1,4 +1,3 @@ - class ExplorerToolbarConfigurator { typealias ButtonAction = (UIBarButtonItem) -> Void let downloadAction: ButtonAction @@ -162,7 +161,7 @@ class ExplorerToolbarConfigurator { var lowestAccessLevel: MEGAShareType = .accessOwner for node in nodes { - let accessLevel = MEGASdkManager.sharedMEGASdk().accessLevel(for: node) + let accessLevel = MEGASdk.shared.accessLevel(for: node) if accessLevel == .accessRead || accessLevel == .accessReadWrite { lowestAccessLevel = accessLevel diff --git a/iMEGA/Home/Scenes/HomeExplore/View/FavouriteExplorerToolbarConfigurator.swift b/iMEGA/Home/Scenes/HomeExplore/View/FavouriteExplorerToolbarConfigurator.swift index 435069c568..a1f35f1d96 100644 --- a/iMEGA/Home/Scenes/HomeExplore/View/FavouriteExplorerToolbarConfigurator.swift +++ b/iMEGA/Home/Scenes/HomeExplore/View/FavouriteExplorerToolbarConfigurator.swift @@ -1,4 +1,3 @@ - final class FavouriteExplorerToolbarConfigurator: ExplorerToolbarConfigurator { let favouriteAction: ButtonAction diff --git a/iMEGA/Home/Scenes/HomeExplore/View/FilesExplorerContainerViewController.swift b/iMEGA/Home/Scenes/HomeExplore/View/FilesExplorerContainerViewController.swift index 1acb5fa84d..4af418b979 100644 --- a/iMEGA/Home/Scenes/HomeExplore/View/FilesExplorerContainerViewController.swift +++ b/iMEGA/Home/Scenes/HomeExplore/View/FilesExplorerContainerViewController.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPermissions import MEGASDKRepo import MEGAUIKit diff --git a/iMEGA/Home/Scenes/HomeExplore/View/FilesExplorerListViewController.swift b/iMEGA/Home/Scenes/HomeExplore/View/FilesExplorerListViewController.swift index 0162e27929..235e5bbb74 100644 --- a/iMEGA/Home/Scenes/HomeExplore/View/FilesExplorerListViewController.swift +++ b/iMEGA/Home/Scenes/HomeExplore/View/FilesExplorerListViewController.swift @@ -1,4 +1,3 @@ - import UIKit class FilesExplorerListViewController: FilesExplorerViewController { diff --git a/iMEGA/Home/Scenes/HomeExplore/View/SearchController/MEGASearchBarView.swift b/iMEGA/Home/Scenes/HomeExplore/View/SearchController/MEGASearchBarView.swift index 0b25322ec4..5ca2e70e96 100644 --- a/iMEGA/Home/Scenes/HomeExplore/View/SearchController/MEGASearchBarView.swift +++ b/iMEGA/Home/Scenes/HomeExplore/View/SearchController/MEGASearchBarView.swift @@ -20,21 +20,18 @@ protocol MEGASearchBarViewDelegate: AnyObject { func didFinishSearchSessionOnSearchController(_ searchController: MEGASearchBarView) } -protocol MEGASearchBarViewEdittingDelegate: AnyObject { +protocol MEGASearchBarViewEditingDelegate: AnyObject { /// Tells the `delegate` that user highlights the search field in the `SearchBarView`. - /// - Parameter searchController: The `SearchBarView` itself. - func didHighlightSearchController(_ searchController: MEGASearchBarView) + func didHighlightSearchBar() /// Tell teh `delegate` that new text `inputText` is updated in the `SearchField` on `SearchBarView`. /// - Parameters: /// - inputText: The newly updated text that in the text field. - /// - searchController: The `SearchBarView` itself. - func didInputText(_ inputText: String, from searchController: MEGASearchBarView) + func didInputText(_ inputText: String) /// Tells the `delegate` the **clear** button is tapped and all text in the `TextField` is removed. - /// - Parameter searchController: The `SearchBarView` itself. - func didClearText(for searchController: MEGASearchBarView) + func didClearText() } final class MEGASearchBarView: UIView, NibOwnerLoadable { @@ -49,7 +46,7 @@ final class MEGASearchBarView: UIView, NibOwnerLoadable { weak var delegate: (any MEGASearchBarViewDelegate)? - weak var edittingDelegate: (any MEGASearchBarViewEdittingDelegate)? + weak var editingDelegate: (any MEGASearchBarViewEditingDelegate)? // MARK: - Initialization @@ -152,17 +149,17 @@ extension MEGASearchBarView: UITextFieldDelegate { guard let textFieldText = textField.text, !textFieldText.isEmpty else { cancelButton.isHidden = false delegate?.didStartSearchSessionOnSearchController(self) - edittingDelegate?.didHighlightSearchController(self) + editingDelegate?.didHighlightSearchBar() return } delegate?.didResumeSearchSessionOnSearchController(self) - edittingDelegate?.didInputText(textFieldText, from: self) + editingDelegate?.didInputText(textFieldText) } func textFieldShouldClear(_ textField: UITextField) -> Bool { let shouldClearText = (textField.text?.isEmpty == false) if shouldClearText { - edittingDelegate?.didClearText(for: self) + editingDelegate?.didClearText() } return shouldClearText } @@ -175,7 +172,7 @@ extension MEGASearchBarView: UITextFieldDelegate { guard let newText = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) else { return true } - edittingDelegate?.didInputText(newText, from: self) + editingDelegate?.didInputText(newText) return true } } diff --git a/iMEGA/Home/Scenes/HomeExplore/ViewModel/AudioExploreViewConfiguration.swift b/iMEGA/Home/Scenes/HomeExplore/ViewModel/AudioExploreViewConfiguration.swift index 918a75a71b..24e6e19671 100644 --- a/iMEGA/Home/Scenes/HomeExplore/ViewModel/AudioExploreViewConfiguration.swift +++ b/iMEGA/Home/Scenes/HomeExplore/ViewModel/AudioExploreViewConfiguration.swift @@ -1,3 +1,4 @@ +import MEGAL10n struct AudioExploreViewConfiguration: FilesExplorerViewConfiguration { diff --git a/iMEGA/Home/Scenes/HomeExplore/ViewModel/DocumentExplorerViewConfiguration.swift b/iMEGA/Home/Scenes/HomeExplore/ViewModel/DocumentExplorerViewConfiguration.swift index 90ede52551..56c39b0eb0 100644 --- a/iMEGA/Home/Scenes/HomeExplore/ViewModel/DocumentExplorerViewConfiguration.swift +++ b/iMEGA/Home/Scenes/HomeExplore/ViewModel/DocumentExplorerViewConfiguration.swift @@ -1,3 +1,4 @@ +import MEGAL10n protocol FilesExplorerViewConfiguration { var title: String { get } diff --git a/iMEGA/Home/Scenes/HomeExplore/ViewModel/FavouritesExplorerGridSource.swift b/iMEGA/Home/Scenes/HomeExplore/ViewModel/FavouritesExplorerGridSource.swift index e67ef04488..42fa1aa44e 100644 --- a/iMEGA/Home/Scenes/HomeExplore/ViewModel/FavouritesExplorerGridSource.swift +++ b/iMEGA/Home/Scenes/HomeExplore/ViewModel/FavouritesExplorerGridSource.swift @@ -1,4 +1,3 @@ - final class FavouritesExplorerGridSource: NSObject { private enum FavouritesSection: Int { @@ -190,7 +189,7 @@ extension FavouritesExplorerGridSource: UICollectionViewDataSource { cell.configureCell(for: node, allowedMultipleSelection: collectionView.allowsMultipleSelection, - sdk: MEGASdkManager.sharedMEGASdk(), + sdk: .shared, delegate: self) return cell } @@ -203,7 +202,7 @@ extension FavouritesExplorerGridSource: DynamicTypeCollectionViewSizing { let cell = FavouritesSection(section: indexPath.section).sizingViewCell() cell.configureCell(for: node, allowedMultipleSelection: collectionView.allowsMultipleSelection, - sdk: MEGASdkManager.sharedMEGASdk(), + sdk: .shared, delegate: self) return cell } diff --git a/iMEGA/Home/Scenes/HomeExplore/ViewModel/FavouritesExplorerViewConfiguration.swift b/iMEGA/Home/Scenes/HomeExplore/ViewModel/FavouritesExplorerViewConfiguration.swift index edbb2780ad..56c6de4d45 100644 --- a/iMEGA/Home/Scenes/HomeExplore/ViewModel/FavouritesExplorerViewConfiguration.swift +++ b/iMEGA/Home/Scenes/HomeExplore/ViewModel/FavouritesExplorerViewConfiguration.swift @@ -1,3 +1,4 @@ +import MEGAL10n struct FavouritesExplorerViewConfiguration: FilesExplorerViewConfiguration { diff --git a/iMEGA/Home/Scenes/HomeExplore/ViewModel/FilterType.swift b/iMEGA/Home/Scenes/HomeExplore/ViewModel/FilterType.swift index 8a73995f66..a6de19e42c 100644 --- a/iMEGA/Home/Scenes/HomeExplore/ViewModel/FilterType.swift +++ b/iMEGA/Home/Scenes/HomeExplore/ViewModel/FilterType.swift @@ -1,3 +1,5 @@ +import MEGAL10n + enum FilterType: String, CaseIterable { case none case allMedia diff --git a/iMEGA/Home/Scenes/HomeExplore/ViewModel/SortOrderType.swift b/iMEGA/Home/Scenes/HomeExplore/ViewModel/SortOrderType.swift index 0001b22ec5..c6ae60a4a5 100644 --- a/iMEGA/Home/Scenes/HomeExplore/ViewModel/SortOrderType.swift +++ b/iMEGA/Home/Scenes/HomeExplore/ViewModel/SortOrderType.swift @@ -1,3 +1,4 @@ +import MEGAL10n enum SortOrderType: String, CaseIterable { case none @@ -49,7 +50,7 @@ enum SortOrderType: String, CaseIterable { key = "" } - return NSLocalizedString(key, comment: "") + return Strings.localized(key, comment: "") } var image: UIImage? { diff --git a/iMEGA/Home/Scenes/HomeExplore/ViewModel/VideoExplorerViewConfiguration.swift b/iMEGA/Home/Scenes/HomeExplore/ViewModel/VideoExplorerViewConfiguration.swift index 8b3841bf1c..e27eaaf1ab 100644 --- a/iMEGA/Home/Scenes/HomeExplore/ViewModel/VideoExplorerViewConfiguration.swift +++ b/iMEGA/Home/Scenes/HomeExplore/ViewModel/VideoExplorerViewConfiguration.swift @@ -1,3 +1,4 @@ +import MEGAL10n struct VideoExplorerViewConfiguration: FilesExplorerViewConfiguration { diff --git a/iMEGA/Home/Scenes/HomeLocalisation.swift b/iMEGA/Home/Scenes/HomeLocalisation.swift index ecf2b79e3f..2810e2e5fe 100644 --- a/iMEGA/Home/Scenes/HomeLocalisation.swift +++ b/iMEGA/Home/Scenes/HomeLocalisation.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n enum HomeLocalisation: String { diff --git a/iMEGA/Home/Scenes/HomeScreenFactory.swift b/iMEGA/Home/Scenes/HomeScreenFactory.swift index 23dabc9a67..45aa0cced7 100644 --- a/iMEGA/Home/Scenes/HomeScreenFactory.swift +++ b/iMEGA/Home/Scenes/HomeScreenFactory.swift @@ -1,15 +1,22 @@ -import Foundation +import Foundation import MEGADomain import MEGAPermissions import MEGASDKRepo +import Search +import SwiftUI -@objc final class HomeScreenFactory: NSObject { + + private var sdk: MEGASdk { + MEGASdk.sharedSdk + } - @objc func createHomeScreen(from tabBarController: MainTabBarController) -> UIViewController { + func createHomeScreen( + from tabBarController: MainTabBarController, + newHomeSearchResultsEnabled: Bool + ) -> UIViewController { let homeViewController = HomeViewController() let navigationController = MEGANavigationController(rootViewController: homeViewController) - let sdk = MEGASdkManager.sharedMEGASdk() let myAvatarViewModel = MyAvatarViewModel( megaNotificationUseCase: MEGANotificationUseCase( @@ -53,34 +60,122 @@ final class HomeScreenFactory: NSObject { homeViewController.recentsViewModel = HomeRecentActionViewModel( permissionHandler: permissionHandler, nodeFavouriteActionUseCase: NodeFavouriteActionUseCase( - nodeFavouriteRepository: NodeFavouriteActionRepository(sdk: MEGASdkManager.sharedMEGASdk()) + nodeFavouriteRepository: NodeFavouriteActionRepository.newRepo ), - saveMediaToPhotosUseCase: SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: sdk), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) + saveMediaToPhotosUseCase: SaveMediaToPhotosUseCase( + downloadFileRepository: DownloadFileRepository(sdk: sdk), + fileCacheRepository: FileCacheRepository.newRepo, + nodeRepository: NodeRepository.newRepo + ) ) homeViewController.bannerViewModel = HomeBannerViewModel( userBannerUseCase: UserBannerUseCase( - userBannerRepository: BannerRepository(sdk: MEGASdkManager.sharedMEGASdk()) + userBannerRepository: BannerRepository.newRepo ), router: HomeBannerRouter(navigationController: navigationController) ) - homeViewController.quickAccessWidgetViewModel = QuickAccessWidgetViewModel(offlineFilesUseCase: OfflineFilesUseCase(repo: OfflineFileFetcherRepository.newRepo)) + homeViewController.quickAccessWidgetViewModel = QuickAccessWidgetViewModel( + offlineFilesUseCase: OfflineFilesUseCase( + repo: OfflineFileFetcherRepository.newRepo + ) + ) navigationController.tabBarItem = UITabBarItem(title: nil, image: Asset.Images.TabBarIcons.home.image, selectedImage: nil) - homeViewController.searchResultViewController = createSearchResultViewController(with: navigationController) + let bridge = SearchResultsBridge() + homeViewController.searchResultsBridge = bridge + + let searchResultViewController = makeSearchResultViewController( + with: navigationController, + bridge: bridge, + newHomeSearchResultsEnabled: newHomeSearchResultsEnabled + ) + + homeViewController.searchResultViewController = searchResultViewController - let router = HomeRouter(navigationController: navigationController, tabBarController: tabBarController) + let router = HomeRouter( + navigationController: navigationController, + tabBarController: tabBarController + ) homeViewController.router = router - homeViewController.homeViewModel = HomeViewModel(shareUseCase: ShareUseCase(repo: ShareRepository.newRepo)) + homeViewController.homeViewModel = HomeViewModel( + shareUseCase: ShareUseCase(repo: ShareRepository.newRepo) + ) return navigationController } - private func createSearchResultViewController( - with navigationController: UINavigationController - ) -> HomeSearchResultViewController { + private func makeSearchResultViewController( + with navigationController: UINavigationController, + bridge: SearchResultsBridge, + newHomeSearchResultsEnabled: Bool + ) -> UIViewController { + if newHomeSearchResultsEnabled { + return makeNewSearchResultsViewController( + with: navigationController, + bridge: bridge + ) + } else { + return makeLegacySearchResultsViewController( + with: navigationController, + bridge: bridge + ) + } + } + + private func makeNewSearchResultsViewController( + with navigationController: UINavigationController, + bridge: SearchResultsBridge + ) -> UIViewController { + + let router = HomeSearchResultRouter( + navigationController: navigationController, + nodeActionViewControllerDelegate: NodeActionViewControllerGenericDelegate( + viewController: navigationController + ) + ) + + // put some real value here to make it work + // this is put here until we have real API calls and real data + let fakeNode: HandleEntity = 221064586114322 + + // this bridge is needed to do a searchBar <-> searchResults -> homeScreen communication without coupling this to + // MEGA app level delegates. Using simple closures to pass data back and forth + let searchBridge = SearchBridge( + selection: { _ in + router.didTapNode(fakeNode) + }, + context: { _ in + // will try to remove the dummy button required by the API on later MR's + router.didTapMoreAction(on: fakeNode, button: UIButton()) + } + ) + + bridge.didInputTextTrampoline = { [weak searchBridge] text in + searchBridge?.queryChanged(text) + } + + bridge.didClearTrampoline = { [weak searchBridge] in + searchBridge?.queryCleaned() + } + + bridge.didFinishSearchingTrampoline = { [weak searchBridge] in + searchBridge?.searchCancelled() + } + + let vm = SearchResultsViewModel( + resultsProvider: NonProductionTestResultsProvider(), + bridge: searchBridge + ) + return UIHostingController(rootView: SearchResultsView(viewModel: vm)) + } + + private func makeLegacySearchResultsViewController( + with navigationController: UINavigationController, + bridge: SearchResultsBridge + ) -> UIViewController { let searchResultViewModel = HomeSearchResultViewModel( searchFileUseCase: SearchFileUseCase( nodeSearchClient: .live, @@ -102,25 +197,25 @@ final class HomeScreenFactory: NSObject { router: HomeSearchResultRouter( navigationController: navigationController, nodeActionViewControllerDelegate: NodeActionViewControllerGenericDelegate( - viewController: navigationController + viewController: navigationController ) ) ) - + let homeSearchResultViewController = HomeSearchResultViewController() homeSearchResultViewController.viewModel = searchResultViewModel homeSearchResultViewController.resultTableViewDataSource - = TableViewProxy( - cellIdentifier: "SearchResultFile", - emptyStateConfiguration: .searchResult, - configureCell: { cell, model in - (cell as? SearchResultFileTableViewCell)?.configure(with: model) - }, - selectionAction: { selectedNode in - searchResultViewModel.didSelectNode(selectedNode.handle) - } - ) - + = TableViewProxy( + cellIdentifier: "SearchResultFile", + emptyStateConfiguration: .searchResult, + configureCell: { cell, model in + (cell as? SearchResultFileTableViewCell)?.configure(with: model) + }, + selectionAction: { selectedNode in + searchResultViewModel.didSelectNode(selectedNode.handle) + } + ) + homeSearchResultViewController.hintTableViewDataSource = TableViewProxy( cellIdentifier: "SearchHint", emptyStateConfiguration: .searchHints, @@ -131,6 +226,21 @@ final class HomeScreenFactory: NSObject { searchResultViewModel.didSelectHint(selectedSearchHint.text) } ) + // setting up the bridge connection instead of just connecting delegates + homeSearchResultViewController.searchHintSelectDelegate = bridge + + bridge.didClearTrampoline = { [weak homeSearchResultViewController] in + homeSearchResultViewController?.didClearText() + } + + bridge.didInputTextTrampoline = { [weak homeSearchResultViewController] text in + homeSearchResultViewController?.didInputText(text) + } + + bridge.didHighlightTrampoline = { [weak homeSearchResultViewController] in + homeSearchResultViewController?.didHighlightSearchBar() + } + return homeSearchResultViewController } } diff --git a/iMEGA/Home/Scenes/HomeSearch/Provider/HomeSearchResultsProviding.swift b/iMEGA/Home/Scenes/HomeSearch/Provider/HomeSearchResultsProviding.swift new file mode 100644 index 0000000000..b52ad9e5b7 --- /dev/null +++ b/iMEGA/Home/Scenes/HomeSearch/Provider/HomeSearchResultsProviding.swift @@ -0,0 +1,59 @@ +import MEGADomain +import MEGASwift +import Search + +final class HomeSearchResultsProviding: SearchResultsProviding { + private let searchFileUseCase: any SearchFileUseCaseProtocol + private let nodeDetailUseCase: any NodeDetailUseCaseProtocol + + init( + searchFileUseCase: some SearchFileUseCaseProtocol, + nodeDetailUseCase: some NodeDetailUseCaseProtocol + ) { + self.searchFileUseCase = searchFileUseCase + self.nodeDetailUseCase = nodeDetailUseCase + } + + func search(queryRequest: SearchQueryEntity) async throws -> SearchResultsEntity { + return try await withAsyncThrowingValue(in: { completion in + searchFileUseCase.searchFiles( + withName: queryRequest.query, + searchPath: .root, + completion: { result in + completion( + .success( + .init( + results: result.map { self.mapNodeToSearchResult($0) }, + // will implement that in FM-797 + chips: [] + ) + ) + ) + } + ) + }) + } + + private func mapNodeToSearchResult(_ node: NodeEntity) -> SearchResult { + return .init( + id: .init(stringLiteral: node.base64Handle), + title: node.name, + description: nodeDetailUseCase.ownerFolder(of: node.handle)?.name ?? "", + // We will fill this later on when we do FM-793 + properties: [], + thumbnailImageData: { await self.loadThumbnail(for: node.handle) }, + type: .node + ) + } + + private func loadThumbnail(for handle: HandleEntity) async -> Data { + return await withAsyncValue(in: { completion in + nodeDetailUseCase.loadThumbnail( + of: handle, + completion: { image in + completion(.success(image?.pngData() ?? Data())) + } + ) + }) + } +} diff --git a/iMEGA/Home/Scenes/HomeSearch/View/HomeSearchResultViewController.swift b/iMEGA/Home/Scenes/HomeSearch/View/HomeSearchResultViewController.swift index b1fabc5fdf..06e5e6af5a 100644 --- a/iMEGA/Home/Scenes/HomeSearch/View/HomeSearchResultViewController.swift +++ b/iMEGA/Home/Scenes/HomeSearch/View/HomeSearchResultViewController.swift @@ -64,24 +64,24 @@ final class HomeSearchResultViewController: UIViewController { } } -// MARK: - MEGASearchControllerEdittingDelegate +// MARK: - MEGASearchControllerEditingDelegate -extension HomeSearchResultViewController: MEGASearchBarViewEdittingDelegate { +extension HomeSearchResultViewController: MEGASearchBarViewEditingDelegate { - func didInputText(_ inputText: String, from searchController: MEGASearchBarView) { + func didInputText(_ inputText: String) { switch inputText.isEmpty { case true: - viewModel.didHilightEmptySearchBar() + viewModel.didHighlightEmptySearchBar() case false: viewModel.didInputText(text: inputText) } } - func didHighlightSearchController(_ searchController: MEGASearchBarView) { - viewModel.didHilightEmptySearchBar() + func didHighlightSearchBar() { + viewModel.didHighlightEmptySearchBar() } - func didClearText(for searchController: MEGASearchBarView) { - viewModel.didHilightEmptySearchBar() + func didClearText() { + viewModel.didHighlightEmptySearchBar() } } diff --git a/iMEGA/Home/Scenes/HomeSearch/ViewModel/HomeSearchResultViewModel.swift b/iMEGA/Home/Scenes/HomeSearch/ViewModel/HomeSearchResultViewModel.swift index 4d0b12a92c..575c953553 100644 --- a/iMEGA/Home/Scenes/HomeSearch/ViewModel/HomeSearchResultViewModel.swift +++ b/iMEGA/Home/Scenes/HomeSearch/ViewModel/HomeSearchResultViewModel.swift @@ -3,7 +3,7 @@ import MEGADomain protocol HomeAccountSearchResultViewModelInputs { - func didHilightEmptySearchBar() + func didHighlightEmptySearchBar() func didInputText(text: String) @@ -59,7 +59,7 @@ final class HomeSearchResultViewModel { extension HomeSearchResultViewModel: HomeAccountSearchResultViewModelInputs { - func didHilightEmptySearchBar() { + func didHighlightEmptySearchBar() { searchFileUseCase.cancelCurrentSearch() let hints = searchFileHistoryUseCase.searchHistoryEntries().map { diff --git a/iMEGA/Home/Scenes/SearchResultsBridge.swift b/iMEGA/Home/Scenes/SearchResultsBridge.swift new file mode 100644 index 0000000000..4b4096d8dd --- /dev/null +++ b/iMEGA/Home/Scenes/SearchResultsBridge.swift @@ -0,0 +1,32 @@ +/// Purpose of this class is to decouple Home screen and its Search bar view from the search results screen which is being replaced +/// by modern generic solution in the Search module +/// It implements delegate protocols that mediate communication SearchBar <-> HomeSearchResultsViewController +/// As we want to hide the new feature behind a flag, we need this intermediate layer to +/// not have to change too much in the existing implementation of search results which will go away eventually + +class SearchResultsBridge: MEGASearchBarViewEditingDelegate, HomeSearchControllerDelegate { + func didSelect(searchText: String) { + didSelectTextTrampoline?(searchText) + } + + func didHighlightSearchBar() { + didHighlightTrampoline?() + } + + func didInputText(_ inputText: String) { + didInputTextTrampoline?(inputText) + } + + func didClearText() { + didClearTrampoline?() + } + + func didFinishSearching() { + didFinishSearchingTrampoline?() + } + var didFinishSearchingTrampoline: (() -> Void)? + var didHighlightTrampoline: (() -> Void)? + var didSelectTextTrampoline: ((String) -> Void)? + var didInputTextTrampoline: ((String) -> Void)? + var didClearTrampoline: (() -> Void)? +} diff --git a/iMEGA/Home/UseCase/Domain/BannerEntity.swift b/iMEGA/Home/UseCase/Domain/BannerEntity.swift deleted file mode 100644 index a2458ff958..0000000000 --- a/iMEGA/Home/UseCase/Domain/BannerEntity.swift +++ /dev/null @@ -1,14 +0,0 @@ -struct BannerEntity { - - let identifier: Int - - let title: String - - let description: String - - let backgroundImageURL: URL - - let imageURL: URL - - let url: URL? -} diff --git a/iMEGA/Home/UseCase/Node/FilesDownloadUseCase.swift b/iMEGA/Home/UseCase/Node/FilesDownloadUseCase.swift index 7888f9fe97..63986cb3cc 100644 --- a/iMEGA/Home/UseCase/Node/FilesDownloadUseCase.swift +++ b/iMEGA/Home/UseCase/Node/FilesDownloadUseCase.swift @@ -1,4 +1,3 @@ - final class FilesDownloadUseCase { private let repo: SDKTransferListenerRepository var nodes: [MEGANode]? diff --git a/iMEGA/Home/UseCase/Node/NodeClipboardOperationUseCase.swift b/iMEGA/Home/UseCase/Node/NodeClipboardOperationUseCase.swift index 0c7b4ecec8..3de3d25944 100644 --- a/iMEGA/Home/UseCase/Node/NodeClipboardOperationUseCase.swift +++ b/iMEGA/Home/UseCase/Node/NodeClipboardOperationUseCase.swift @@ -1,4 +1,3 @@ - struct NodeClipboardOperationUseCase { private let repo: SDKNodeClipboardOperationRepository diff --git a/iMEGA/Home/UseCase/Node/NodeDetails/MockNodeDetailsUseCase.swift b/iMEGA/Home/UseCase/Node/NodeDetails/MockNodeDetailsUseCase.swift new file mode 100644 index 0000000000..29ba1020fb --- /dev/null +++ b/iMEGA/Home/UseCase/Node/NodeDetails/MockNodeDetailsUseCase.swift @@ -0,0 +1,23 @@ +import Foundation +@testable import MEGA +import MEGADomain + +final class MockNodeDetailUseCase: NodeDetailUseCaseProtocol { + private let owner: NodeEntity? + private let thumbnail: UIImage? + + init( + owner: NodeEntity? = nil, + thumbnail: UIImage? = nil + ) { + self.owner = owner + self.thumbnail = thumbnail + } + func ownerFolder(of node: HandleEntity) -> NodeEntity? { + owner + } + + func loadThumbnail(of node: HandleEntity, completion: @escaping (UIImage?) -> Void) { + completion(thumbnail) + } +} diff --git a/iMEGA/Home/UseCase/Node/NodeDetailsUseCase.swift b/iMEGA/Home/UseCase/Node/NodeDetails/NodeDetailsUseCase.swift similarity index 100% rename from iMEGA/Home/UseCase/Node/NodeDetailsUseCase.swift rename to iMEGA/Home/UseCase/Node/NodeDetails/NodeDetailsUseCase.swift diff --git a/iMEGA/Home/UseCase/Node/NodeThumbnailHomeUseCase.swift b/iMEGA/Home/UseCase/Node/NodeThumbnailHomeUseCase.swift index b0f182e036..59998797b7 100644 --- a/iMEGA/Home/UseCase/Node/NodeThumbnailHomeUseCase.swift +++ b/iMEGA/Home/UseCase/Node/NodeThumbnailHomeUseCase.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n import MEGASwift import UIKit diff --git a/iMEGA/Home/UseCase/Node/Search/SearchFileUseCase/MockSearchFileUseCase.swift b/iMEGA/Home/UseCase/Node/Search/SearchFileUseCase/MockSearchFileUseCase.swift new file mode 100644 index 0000000000..45f06bcd18 --- /dev/null +++ b/iMEGA/Home/UseCase/Node/Search/SearchFileUseCase/MockSearchFileUseCase.swift @@ -0,0 +1,24 @@ +import Foundation +@testable import MEGA +import MEGADomain +import MEGAFoundation + +final class MockSearchFileUseCase: SearchFileUseCaseProtocol { + private let nodes: [NodeEntity] + + init( + nodes: [NodeEntity] = [] + ) { + self.nodes = nodes + } + + func searchFiles( + withName name: String, + searchPath: SearchFileRootPath, + completion: @escaping ([NodeEntity]) -> Void + ) { + completion(nodes.filter { $0.name.contains(name) }) + } + + func cancelCurrentSearch() {} +} diff --git a/iMEGA/Home/UseCase/Node/Search/SearchFileUseCase.swift b/iMEGA/Home/UseCase/Node/Search/SearchFileUseCase/SearchFileUseCase.swift similarity index 100% rename from iMEGA/Home/UseCase/Node/Search/SearchFileUseCase.swift rename to iMEGA/Home/UseCase/Node/Search/SearchFileUseCase/SearchFileUseCase.swift diff --git a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentRouter.swift b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentRouter.swift index 71c882131a..4804a6974b 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentRouter.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentRouter.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation import MEGASDKRepo import MEGASwiftUI diff --git a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewController+ContextMenu.swift b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewController+ContextMenu.swift index 7dae4c2eda..1009920420 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewController+ContextMenu.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewController+ContextMenu.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo extension AlbumContentViewController { diff --git a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewController.swift b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewController.swift index c9c4cf07b3..5fb5fdb50d 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewController.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewController.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation import MEGAUIKit import SwiftUI diff --git a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewModel.swift b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewModel.swift index 66e66b6bb4..562751e821 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewModel.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumContentViewModel.swift @@ -1,6 +1,8 @@ import Combine import Foundation +import MEGAAnalyticsiOS import MEGADomain +import MEGAL10n import MEGAPresentation enum AlbumContentAction: ActionType { @@ -39,6 +41,7 @@ final class AlbumContentViewModel: ViewModelType { private let router: any AlbumContentRouting private let featureFlagProvider: any FeatureFlagProviderProtocol private let shareAlbumUseCase: any ShareAlbumUseCaseProtocol + private let tracker: any AnalyticsTracking private var loadingTask: Task? private var photos = [AlbumPhotoEntity]() @@ -73,7 +76,8 @@ final class AlbumContentViewModel: ViewModelType { router: some AlbumContentRouting, newAlbumPhotosToAdd: [NodeEntity]? = nil, alertViewModel: TextFieldAlertViewModel, - featureFlagProvider: some FeatureFlagProviderProtocol = DIContainer.featureFlagProvider + featureFlagProvider: some FeatureFlagProviderProtocol = DIContainer.featureFlagProvider, + tracker: some AnalyticsTracking = DIContainer.tracker ) { self.album = album self.newAlbumPhotosToAdd = newAlbumPhotosToAdd @@ -84,6 +88,7 @@ final class AlbumContentViewModel: ViewModelType { self.router = router self.alertViewModel = alertViewModel self.featureFlagProvider = featureFlagProvider + self.tracker = tracker setupSubscription() setupAlbumModification() @@ -94,6 +99,7 @@ final class AlbumContentViewModel: ViewModelType { func dispatch(_ action: AlbumContentAction) { switch action { case .onViewReady: + tracker.trackAnalyticsEvent(with: AlbumContentScreenEvent()) loadingTask = Task { await addNewAlbumPhotosIfNeeded() await loadNodes() @@ -116,6 +122,7 @@ final class AlbumContentViewModel: ViewModelType { isPhotoSelectionHidden = isSelectHidden invokeCommand?(.rebuildContextMenu) case .shareLink: + tracker.trackAnalyticsEvent(with: AlbumContentShareLinkMenuToolbarEvent()) router.showShareLink(album: album) case .removeLink: removeSharedLink() @@ -331,7 +338,7 @@ final class AlbumContentViewModel: ViewModelType { @MainActor private func onAlbumRenameSuccess(with newName: String) { - album = album.update(name: newName) + album.name = newName invokeCommand?(.updateNavigationTitle) } @@ -397,7 +404,7 @@ final class AlbumContentViewModel: ViewModelType { return } if setEntity.changeTypes.contains(.name) && albumName != setEntity.name { - album = album.update(name: setEntity.name) + album.name = setEntity.name invokeCommand?(.updateNavigationTitle) } if setEntity.changeTypes.contains(.cover) { diff --git a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumCover/AlbumCoverPickerView.swift b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumCover/AlbumCoverPickerView.swift index df1bd6801a..dd0fffe79a 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumCover/AlbumCoverPickerView.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumCover/AlbumCoverPickerView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGASwiftUI import SwiftUI diff --git a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumToolbarConfigurator.swift b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumToolbarConfigurator.swift index 5b6824af4e..f875baf854 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumToolbarConfigurator.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumToolbarConfigurator.swift @@ -1,4 +1,3 @@ - final class AlbumToolbarConfigurator: ExplorerToolbarConfigurator { let favouriteAction: ButtonAction let removeToRubbishBinAction: ButtonAction diff --git a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumToolbarProvider.swift b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumToolbarProvider.swift index 46a11c6c1e..22e76a2dab 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumToolbarProvider.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumContent/AlbumToolbarProvider.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo import UIKit @@ -155,11 +156,7 @@ extension AlbumContentViewController: AlbumToolbarProvider { return } - let favoriteUseCase = NodeFavouriteActionUseCase( - nodeFavouriteRepository: NodeFavouriteActionRepository( - sdk: MEGASdkManager.sharedMEGASdk() - ) - ) + let favoriteUseCase = NodeFavouriteActionUseCase(nodeFavouriteRepository: NodeFavouriteActionRepository.newRepo) selectedNodes.forEach { node in if node.isFavourite { @@ -184,7 +181,9 @@ extension AlbumContentViewController: AlbumToolbarProvider { endEditingMode() - let saveMediaUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: MEGASdkManager.sharedMEGASdk()), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) + let saveMediaUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: MEGASdk.shared), + fileCacheRepository: FileCacheRepository.newRepo, + nodeRepository: NodeRepository.newRepo) TransfersWidgetViewController.sharedTransfer().setProgressViewInKeyWindow() TransfersWidgetViewController.sharedTransfer().progressView?.showWidgetIfNeeded() diff --git a/iMEGA/Media Consumption/Album Library/AlbumContentPickerView.swift b/iMEGA/Media Consumption/Album Library/AlbumContentPickerView.swift index a38785120c..3d5ed15049 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumContentPickerView.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumContentPickerView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGASwiftUI import SwiftUI diff --git a/iMEGA/Media Consumption/Album Library/AlbumContentPickerViewModel.swift b/iMEGA/Media Consumption/Album Library/AlbumContentPickerViewModel.swift index f87b7a810b..6c1d82a851 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumContentPickerViewModel.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumContentPickerViewModel.swift @@ -1,6 +1,7 @@ import Combine import Foundation import MEGADomain +import MEGAL10n final class AlbumContentPickerViewModel: ObservableObject { @@ -71,7 +72,7 @@ final class AlbumContentPickerViewModel: ObservableObject { photoLibraryContentViewModel.selection.$photos .compactMap { [weak self] photos in guard let self = self else { return nil } - return photos.isEmpty ? self.normalNavigationTitle : self.navigationTitle(forNumberOfItems: photos.count) + return photos.isEmpty ? self.normalNavigationTitle : Strings.Localizable.General.Format.itemsSelected(photos.count) } .receive(on: DispatchQueue.main) .assign(to: &$navigationTitle) @@ -159,10 +160,6 @@ final class AlbumContentPickerViewModel: ObservableObject { } } - private func navigationTitle(forNumberOfItems num: Int) -> String { - num == 1 ? Strings.Localizable.oneItemSelected(1): Strings.Localizable.itemsSelected(num) - } - @MainActor private func updatePhotoSourceLocationNavigationTitleIfRequired() { guard photoSourceLocationNavigationTitle != photoSourceLocation.localization else { diff --git a/iMEGA/Media Consumption/Album Library/AlbumListView.swift b/iMEGA/Media Consumption/Album Library/AlbumListView.swift index 688efdd6db..8999e3cc30 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumListView.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumListView.swift @@ -54,7 +54,7 @@ struct AlbumListView: View { viewModel.onViewAppeared() } .onDisappear { - viewModel.onViewDissappeared() + viewModel.onViewDisappeared() } .progressViewStyle(.circular) .environment(\.editMode, $editMode) diff --git a/iMEGA/Media Consumption/Album Library/AlbumListViewModel.swift b/iMEGA/Media Consumption/Album Library/AlbumListViewModel.swift index 966fe2e71d..5a614171b7 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumListViewModel.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumListViewModel.swift @@ -1,5 +1,8 @@ import Combine +import MEGAAnalyticsiOS import MEGADomain +import MEGAL10n +import MEGAPresentation import SwiftUI @MainActor @@ -17,7 +20,7 @@ final class AlbumListViewModel: NSObject, ObservableObject { } } @Published var showShareAlbumLinks = false - + lazy var selection = AlbumSelection() var albumCreationAlertMsg: String? @@ -38,20 +41,23 @@ final class AlbumListViewModel: NSObject, ObservableObject { private let usecase: any AlbumListUseCaseProtocol private let albumModificationUseCase: any AlbumModificationUseCaseProtocol private let shareAlbumUseCase: any ShareAlbumUseCaseProtocol + private let tracker: any AnalyticsTracking private(set) var alertViewModel: TextFieldAlertViewModel private var subscriptions = Set() private(set) var viewIsVisiblePublisher = PassthroughSubject() private weak var photoAlbumContainerViewModel: PhotoAlbumContainerViewModel? - init(usecase: any AlbumListUseCaseProtocol, - albumModificationUseCase: any AlbumModificationUseCaseProtocol, - shareAlbumUseCase: any ShareAlbumUseCaseProtocol, + init(usecase: some AlbumListUseCaseProtocol, + albumModificationUseCase: some AlbumModificationUseCaseProtocol, + shareAlbumUseCase: some ShareAlbumUseCaseProtocol, + tracker: some AnalyticsTracking, alertViewModel: TextFieldAlertViewModel, photoAlbumContainerViewModel: PhotoAlbumContainerViewModel? = nil) { self.usecase = usecase self.albumModificationUseCase = albumModificationUseCase self.shareAlbumUseCase = shareAlbumUseCase + self.tracker = tracker self.alertViewModel = alertViewModel self.photoAlbumContainerViewModel = photoAlbumContainerViewModel super.init() @@ -62,7 +68,7 @@ final class AlbumListViewModel: NSObject, ObservableObject { assignAlbumNameValidator() subscribeToEditMode() - subscibeToShareAlbumLinks() + subscribeToShareAlbumLinks() } func columns(horizontalSizeClass: UserInterfaceSizeClass?) -> [GridItem] { @@ -104,7 +110,7 @@ final class AlbumListViewModel: NSObject, ObservableObject { createAlbumTask = Task { do { let newAlbum = try await usecase.createUserAlbum(with: name) - + newlyAddedAlbum = await usecase.hasNoPhotosAndVideos() ? nil : newAlbum photoAlbumContainerViewModel?.shouldShowSelectBarButton = true } catch { @@ -164,12 +170,14 @@ final class AlbumListViewModel: NSObject, ObservableObject { // MARK: - Private private func systemAlbums() async -> [AlbumEntity] { do { - return try await usecase.systemAlbums().map({ album in + return try await usecase.systemAlbums().map { album in if let localizedAlbumName = localisedName(forAlbumType: album.type) { - return album.update(name: localizedAlbumName) + var album = album + album.name = localizedAlbumName + return album } return album - }) + } } catch { MEGALogError("Error loading system albums: \(error.localizedDescription)") return [] @@ -229,7 +237,7 @@ final class AlbumListViewModel: NSObject, ObservableObject { let hudMessage = albumIds.count == 1 ? Strings.Localizable.CameraUploads.Albums.removeShareLinkSuccessMessage(1) : Strings.Localizable.CameraUploads.Albums.removeShareLinkSuccessMessage(albums.count) albumHudMessage = AlbumHudMessage(message: hudMessage, icon: Asset.Images.Hud.hudSuccess.image) - + photoAlbumContainerViewModel?.editMode = .inactive } @@ -255,7 +263,7 @@ final class AlbumListViewModel: NSObject, ObservableObject { secondaryButton: .cancel(Text(Strings.Localizable.cancel)) ) } - + func newAlbumName() -> String { let newAlbumName = Strings.Localizable.CameraUploads.Albums.Create.Alert.placeholder let names = Set(albums.filter { $0.name.hasPrefix(newAlbumName) }.map { $0.name }) @@ -288,13 +296,15 @@ final class AlbumListViewModel: NSObject, ObservableObject { albumCreationAlertMsg = nil self.album = album + + tracker.trackAnalyticsEvent(with: album.makeAlbumSelectedEvent(selectionType: .single)) } func onViewAppeared() { viewIsVisiblePublisher.send(true) } - func onViewDissappeared() { + func onViewDisappeared() { viewIsVisiblePublisher.send(false) } @@ -323,7 +333,7 @@ final class AlbumListViewModel: NSObject, ObservableObject { self?.loadAlbums() } .store(in: &subscriptions) - + photoAlbumContainerViewModel?.$showDeleteAlbumAlert .dropFirst() .sink { [weak self] _ in @@ -364,7 +374,7 @@ final class AlbumListViewModel: NSObject, ObservableObject { albumRemoveShareLinkTask = nil } - private func subscibeToShareAlbumLinks() { + private func subscribeToShareAlbumLinks() { guard let photoAlbumContainerViewModel else { return } photoAlbumContainerViewModel.$showShareAlbumLinks .dropFirst() diff --git a/iMEGA/Media Consumption/Album Library/AlbumListViewRouter.swift b/iMEGA/Media Consumption/Album Library/AlbumListViewRouter.swift index e7630bbc97..38b3530a7d 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumListViewRouter.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumListViewRouter.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import MEGAPresentation import MEGASDKRepo import SwiftUI @@ -44,6 +45,7 @@ struct AlbumListViewRouter: AlbumListViewRouting, Routing { ), albumModificationUseCase: AlbumModificationUseCase(userAlbumRepo: userAlbumRepo), shareAlbumUseCase: ShareAlbumUseCase(shareAlbumRepository: ShareAlbumRepository.newRepo), + tracker: DIContainer.tracker, alertViewModel: TextFieldAlertViewModel(title: Strings.Localizable.CameraUploads.Albums.Create.Alert.title, placeholderText: Strings.Localizable.CameraUploads.Albums.Create.Alert.placeholder, affirmativeButtonTitle: Strings.Localizable.createFolderButton, diff --git a/iMEGA/Media Consumption/Album Library/AlbumNameValidator.swift b/iMEGA/Media Consumption/Album Library/AlbumNameValidator.swift index 194c1e3f3b..e266609ace 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumNameValidator.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumNameValidator.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n struct AlbumNameValidator { let existingAlbumNames: () -> [String] diff --git a/iMEGA/Media Consumption/Album Library/AlbumSelection.swift b/iMEGA/Media Consumption/Album Library/AlbumSelection.swift index a1d00b5659..4033a9ab24 100644 --- a/iMEGA/Media Consumption/Album Library/AlbumSelection.swift +++ b/iMEGA/Media Consumption/Album Library/AlbumSelection.swift @@ -1,4 +1,3 @@ - import Combine import MEGADomain import SwiftUI diff --git a/iMEGA/Media Consumption/Album Library/Alert/UIAlertController+Extension.swift b/iMEGA/Media Consumption/Album Library/Alert/UIAlertController+Extension.swift index b087cc611d..b06f835ad9 100644 --- a/iMEGA/Media Consumption/Album Library/Alert/UIAlertController+Extension.swift +++ b/iMEGA/Media Consumption/Album Library/Alert/UIAlertController+Extension.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n extension UIAlertController { convenience init(alert: TextFieldAlertViewModel) { diff --git a/iMEGA/Media Consumption/Album Library/Cell/AlbumCell.swift b/iMEGA/Media Consumption/Album Library/Cell/AlbumCell.swift index 614afd38dd..3ae6900040 100644 --- a/iMEGA/Media Consumption/Album Library/Cell/AlbumCell.swift +++ b/iMEGA/Media Consumption/Album Library/Cell/AlbumCell.swift @@ -51,11 +51,8 @@ struct AlbumCell: View { } } .opacity(viewModel.opacity) - .onAppear { - viewModel.loadAlbumThumbnail() - } - .onDisappear { - viewModel.cancelLoading() + .taskForiOS14 { + await viewModel.loadAlbumThumbnail() } .gesture(viewModel.editMode.isEditing ? tap : nil) } diff --git a/iMEGA/Media Consumption/Album Library/Cell/AlbumCellViewModel.swift b/iMEGA/Media Consumption/Album Library/Cell/AlbumCellViewModel.swift index 6b7cade0a0..8d2c1d6fcb 100644 --- a/iMEGA/Media Consumption/Album Library/Cell/AlbumCellViewModel.swift +++ b/iMEGA/Media Consumption/Album Library/Cell/AlbumCellViewModel.swift @@ -29,8 +29,7 @@ final class AlbumCellViewModel: ObservableObject { let album: AlbumEntity private let thumbnailUseCase: any ThumbnailUseCaseProtocol - - private var loadingTask: Task? + private let tracker: any AnalyticsTracking let selection: AlbumSelection @@ -46,11 +45,13 @@ final class AlbumCellViewModel: ObservableObject { thumbnailUseCase: any ThumbnailUseCaseProtocol, album: AlbumEntity, selection: AlbumSelection, - featureFlagProvider: some FeatureFlagProviderProtocol = DIContainer.featureFlagProvider + featureFlagProvider: some FeatureFlagProviderProtocol = DIContainer.featureFlagProvider, + tracker: some AnalyticsTracking = DIContainer.tracker ) { self.thumbnailUseCase = thumbnailUseCase self.album = album self.selection = selection + self.tracker = tracker title = album.name numberOfNodes = album.count @@ -67,7 +68,8 @@ final class AlbumCellViewModel: ObservableObject { subscribeToEditMode() } - func loadAlbumThumbnail() { + @MainActor + func loadAlbumThumbnail() async { guard let coverNode = album.coverNode, thumbnailContainer.type == .placeholder else { return @@ -75,19 +77,15 @@ final class AlbumCellViewModel: ObservableObject { if !isLoading { isLoading.toggle() } - loadingTask = Task { - await loadThumbnail(for: coverNode) - } - } - - func cancelLoading() { - isLoading = false - loadingTask?.cancel() + await loadThumbnail(for: coverNode) } func onAlbumTap() { guard !album.systemAlbum else { return } isSelected.toggle() + + tracker.trackAnalyticsEvent(with: album.makeAlbumSelectedEvent( + selectionType: isSelected ? .multiadd : .multiremove)) } // MARK: Private diff --git a/iMEGA/Media Consumption/Album Library/Cell/CreateAlbumCell.swift b/iMEGA/Media Consumption/Album Library/Cell/CreateAlbumCell.swift index 0a2ef9e9e0..a80525dac7 100644 --- a/iMEGA/Media Consumption/Album Library/Cell/CreateAlbumCell.swift +++ b/iMEGA/Media Consumption/Album Library/Cell/CreateAlbumCell.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct CreateAlbumCell: View { diff --git a/iMEGA/Media Consumption/Album Library/Cell/CreateAlbumCellViewModel.swift b/iMEGA/Media Consumption/Album Library/Cell/CreateAlbumCellViewModel.swift index d279445db3..572f662e48 100644 --- a/iMEGA/Media Consumption/Album Library/Cell/CreateAlbumCellViewModel.swift +++ b/iMEGA/Media Consumption/Album Library/Cell/CreateAlbumCellViewModel.swift @@ -5,7 +5,7 @@ final class CreateAlbumCellViewModel: ObservableObject { @Published var orientation = UIDevice.current.orientation @Published var plusIconSize: CGFloat = 0 - private var cancellable: Cancellable? + private var cancellable: (any Cancellable)? private var isLandscape: Bool { orientation == .landscapeLeft || orientation == .landscapeRight diff --git a/iMEGA/Media Consumption/Album Library/EmptyAlbumView.swift b/iMEGA/Media Consumption/Album Library/EmptyAlbumView.swift index 51ddf476f6..fa973698f6 100644 --- a/iMEGA/Media Consumption/Album Library/EmptyAlbumView.swift +++ b/iMEGA/Media Consumption/Album Library/EmptyAlbumView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct EmptyAlbumView: View { diff --git a/iMEGA/Media Consumption/Album Library/Extensions/AlbumEntity+Analytics.swift b/iMEGA/Media Consumption/Album Library/Extensions/AlbumEntity+Analytics.swift new file mode 100644 index 0000000000..0f8c7a9372 --- /dev/null +++ b/iMEGA/Media Consumption/Album Library/Extensions/AlbumEntity+Analytics.swift @@ -0,0 +1,10 @@ +import MEGAAnalyticsiOS +import MEGADomain + +extension AlbumEntity { + func makeAlbumSelectedEvent(selectionType: AlbumSelected.SelectionType) -> AlbumSelectedEvent { + AlbumSelectedEvent(selectionType: selectionType, + imageCount: metaData?.imageCount.toKotlinInt(), + videoCount: metaData?.videoCount.toKotlinInt()) + } +} diff --git a/iMEGA/Media Consumption/Links/ImportAlbumView.swift b/iMEGA/Media Consumption/Links/ImportAlbumView.swift index 08019923d7..b6cf4ae804 100644 --- a/iMEGA/Media Consumption/Links/ImportAlbumView.swift +++ b/iMEGA/Media Consumption/Links/ImportAlbumView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGASwiftUI import SwiftUI @@ -17,6 +18,7 @@ struct ImportAlbumView: View, DismissibleContentView { @State private var publicAlbumLoadingTask: Task? var body: some View { + ZStack { EmptyView() .decryptionKeyMissingAlert(isPresented: $viewModel.showingDecryptionKeyAlert, @@ -63,6 +65,7 @@ struct ImportAlbumView: View, DismissibleContentView { dismissButton: .cancel(Text(Strings.Localizable.AlbumLink.InvalidAlbum.Alert.dissmissButtonTitle), action: dismissImportAlbumScreen)) } + .alertPhotosPermission(isPresented: $viewModel.showPhotoPermissionAlert) .sheet(isPresented: $viewModel.showImportAlbumLocation) { BrowserView(browserAction: .saveToCloudDrive, isChildBrowser: true, @@ -73,10 +76,18 @@ struct ImportAlbumView: View, DismissibleContentView { .fullScreenCover(isPresented: $viewModel.showStorageQuotaWillExceed) { CustomModalAlertView(mode: .storageQuotaWillExceed(displayMode: .albumLink)) } - .onAppear { viewModel.onViewAppear() } + .onAppear { + viewModel.onViewAppear() + } .onReceive(viewModel.$showLoading.dropFirst()) { $0 ? SVProgressHUD.show() : SVProgressHUD.dismiss() } + .onReceive(viewModel.$showNoInternetConnection.dropFirst()) { + guard $0 else { return } + SVProgressHUD.dismiss() + SVProgressHUD.show(Asset.Images.Hud.hudForbidden.image, + status: Strings.Localizable.noInternetConnection) + } } private var navigationBar: some View { @@ -162,7 +173,7 @@ struct ImportAlbumView: View, DismissibleContentView { } ToolbarImageButton(image: Asset.Images.NodeActions.saveToPhotos.image, isDisabled: viewModel.isToolbarButtonsDisabled) { - + Task { await viewModel.saveToPhotos() } } Spacer() shareLinkButton() @@ -194,8 +205,8 @@ struct ImportAlbumView: View, DismissibleContentView { @ViewBuilder private func snackBar(toolbarGeometry: GeometryProxy) -> some View { - if viewModel.showSnackBar { - SnackBarView(viewModel: viewModel.snackBarViewModel()) + if let snackBarViewModel = viewModel.snackBarViewModel { + SnackBarView(viewModel: snackBarViewModel) .offset(y: -(Constants.snackBarVerticalOffSet + toolbarGeometry.size.height)) } } diff --git a/iMEGA/Media Consumption/Links/ImportAlbumViewModel.swift b/iMEGA/Media Consumption/Links/ImportAlbumViewModel.swift index e278b447aa..293169f992 100644 --- a/iMEGA/Media Consumption/Links/ImportAlbumViewModel.swift +++ b/iMEGA/Media Consumption/Links/ImportAlbumViewModel.swift @@ -1,6 +1,8 @@ import Combine import MEGAAnalyticsiOS import MEGADomain +import MEGAL10n +import MEGAPermissions import MEGAPresentation import MEGASwift import SwiftUI @@ -9,18 +11,23 @@ final class ImportAlbumViewModel: ObservableObject { enum Constants { static let disabledOpacity = 0.3 } + // Private State private let publicAlbumUseCase: any PublicAlbumUseCaseProtocol private let albumNameUseCase: any AlbumNameUseCaseProtocol private let accountStorageUseCase: any AccountStorageUseCaseProtocol private let importPublicAlbumUseCase: any ImportPublicAlbumUseCaseProtocol + private let saveMediaUseCase: any SaveMediaToPhotosUseCaseProtocol + private let permissionHandler: any DevicePermissionsHandling private let tracker: any AnalyticsTracking + private weak var transferWidgetResponder: (any TransferWidgetResponderProtocol)? + private let monitorUseCase: any NetworkMonitorUseCaseProtocol private var publicLinkWithDecryptionKey: URL? private var subscriptions = Set() private var showSnackBarSubscription: AnyCancellable? - private var snackBarMessage = "" private var renamedAlbum: String? - + + // Public State private(set) var importAlbumTask: Task? private(set) var reservedAlbumNames: [String]? @@ -43,13 +50,16 @@ final class ImportAlbumViewModel: ObservableObject { @Published var showStorageQuotaWillExceed = false @Published var importFolderLocation: NodeEntity? @Published var showRenameAlbumAlert = false - @Published var showSnackBar = false + @Published var showPhotoPermissionAlert = false + @Published var showNoInternetConnection = false @Published private(set) var isSelectionEnabled = false @Published private(set) var selectButtonOpacity = 0.0 @Published private(set) var publicAlbumName: String? @Published private(set) var selectionNavigationTitle: String = "" @Published private(set) var isToolbarButtonsDisabled = true @Published private(set) var showLoading = false + @Published private(set) var snackBarViewModel: SnackBarViewModel? + @Published private(set) var isShareLinkButtonDisabled = true private var albumLink: String { @@ -82,13 +92,21 @@ final class ImportAlbumViewModel: ObservableObject { accountStorageUseCase: some AccountStorageUseCaseProtocol, importPublicAlbumUseCase: some ImportPublicAlbumUseCaseProtocol, accountUseCase: some AccountUseCaseProtocol, - tracker: some AnalyticsTracking) { + saveMediaUseCase: some SaveMediaToPhotosUseCaseProtocol, + transferWidgetResponder: (some TransferWidgetResponderProtocol)?, + permissionHandler: some DevicePermissionsHandling, + tracker: some AnalyticsTracking, + monitorUseCase: some NetworkMonitorUseCaseProtocol) { self.publicLink = publicLink self.publicAlbumUseCase = publicAlbumUseCase self.albumNameUseCase = albumNameUseCase self.accountStorageUseCase = accountStorageUseCase self.importPublicAlbumUseCase = importPublicAlbumUseCase + self.saveMediaUseCase = saveMediaUseCase + self.transferWidgetResponder = transferWidgetResponder + self.permissionHandler = permissionHandler self.tracker = tracker + self.monitorUseCase = monitorUseCase photoLibraryContentViewModel = PhotoLibraryContentViewModel(library: PhotoLibrary(), contentMode: .albumLink) @@ -123,7 +141,7 @@ final class ImportAlbumViewModel: ObservableObject { publicLinkWithDecryptionKey = linkWithDecryption await loadPublicAlbum() } - + func enablePhotoLibraryEditMode(_ enable: Bool) { photoLibraryContentViewModel.selection.editMode = enable ? .active : .inactive } @@ -138,6 +156,10 @@ final class ImportAlbumViewModel: ObservableObject { @MainActor func importAlbum() async { + guard monitorUseCase.isConnected() else { + showNoInternetConnection = true + return + } do { try await accountStorageUseCase.refreshCurrentAccountDetails() } catch { @@ -145,7 +167,7 @@ final class ImportAlbumViewModel: ObservableObject { return } - guard !accountStorageUseCase.willStorageQuotaExceed(after: photoLibraryContentViewModel.photosToImport) else { + guard !accountStorageUseCase.willStorageQuotaExceed(after: photoLibraryContentViewModel.photosToAction) else { showStorageQuotaWillExceed.toggle() return } @@ -158,24 +180,46 @@ final class ImportAlbumViewModel: ObservableObject { showImportAlbumLocation.toggle() } - func snackBarViewModel() -> SnackBarViewModel { - let snackBar = SnackBar(message: snackBarMessage) - let viewModel = SnackBarViewModel(snackBar: snackBar) + @MainActor + func saveToPhotos() async { + guard monitorUseCase.isConnected() else { + showNoInternetConnection = true + return + } + let photosToSave = photoLibraryContentViewModel.photosToAction + + guard photosToSave.isNotEmpty else { + return + } - showSnackBarSubscription = viewModel.$isShowSnackBar - .receive(on: DispatchQueue.main) - .sink { [weak self] isShown in - guard let self else { return } - if isShown { - snackBarMessage = "" - } - showSnackBar = isShown - } + let granted = await permissionHandler.requestPhotoLibraryAccessPermissions() - return viewModel + guard granted else { + showPhotoPermissionAlert = true + MEGALogError("[Import Album] PhotoLibraryAccessPermissions not granted") + return + } + + transferWidgetResponder?.setProgressViewInKeyWindow() + transferWidgetResponder?.updateProgressView(bottomConstant: -140) + transferWidgetResponder?.bringProgressToFrontKeyWindowIfNeeded() + transferWidgetResponder?.showWidgetIfNeeded() + + showSnackBar(message: Strings.Localizable.General.SaveToPhotos.started(photosToSave.count)) + + do { + try await saveMediaUseCase.saveToPhotos(nodes: photosToSave) + } catch { + MEGALogError("[Import Album] Error saving media nodes: \(error)") + showSnackBar(message: error.localizedDescription) + } } - + func renameAlbum(newName: String) { + guard monitorUseCase.isConnected() else { + showNoInternetConnection = true + return + } renamedAlbum = newName showImportAlbumLocation.toggle() } @@ -212,6 +256,25 @@ final class ImportAlbumViewModel: ObservableObject { publicLinkWithDecryptionKey = nil } + private func makeSnackBarViewModel(message: String) -> SnackBarViewModel { + + showSnackBarSubscription?.cancel() + + let snackBar = SnackBar(message: message) + let viewModel = SnackBarViewModel(snackBar: snackBar) + + showSnackBarSubscription = viewModel.$isShowSnackBar + .filter { !$0 } + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self else { return } + snackBarViewModel = nil + } + return viewModel + } + + // MARK: Subscriptions + private func subscribeToSelection() { subscribeToEditMode() subscribeToSelectionHidden() @@ -225,10 +288,8 @@ final class ImportAlbumViewModel: ObservableObject { switch $0 { case 0: return Strings.Localizable.selectTitle - case 1: - return Strings.Localizable.oneItemSelected(1) default: - return Strings.Localizable.itemsSelected($0) + return Strings.Localizable.General.Format.itemsSelected($0) } }.assign(to: &$selectionNavigationTitle) @@ -306,14 +367,18 @@ final class ImportAlbumViewModel: ObservableObject { .compactMap { $0 } .sink { [weak self] in guard let self else { return } - handeImportFolderSelection(folder: $0) + handleImportFolderSelection(folder: $0) } .store(in: &subscriptions) } - private func handeImportFolderSelection(folder: NodeEntity) { + private func handleImportFolderSelection(folder: NodeEntity) { + guard monitorUseCase.isConnected() else { + showNoInternetConnection = true + return + } guard let albumName else { return } - let photos = photoLibraryContentViewModel.photosToImport + let photos = photoLibraryContentViewModel.photosToAction importAlbumTask = Task { [weak self] in guard let self else { return } @@ -345,8 +410,12 @@ final class ImportAlbumViewModel: ObservableObject { @MainActor private func showSnackBar(message: String) { - snackBarMessage = message - showSnackBar.toggle() + guard let snackBarViewModel else { + snackBarViewModel = makeSnackBarViewModel(message: message) + return + } + + snackBarViewModel.update(snackBar: SnackBar(message: message)) } @MainActor @@ -356,7 +425,7 @@ final class ImportAlbumViewModel: ObservableObject { } private extension PhotoLibraryContentViewModel { - var photosToImport: [NodeEntity] { + var photosToAction: [NodeEntity] { if selection.editMode.isEditing { return Array(selection.photos.values) } diff --git a/iMEGA/Media Consumption/Photo Library/Card View/PhotoLibraryModeCardView.swift b/iMEGA/Media Consumption/Photo Library/Card View/PhotoLibraryModeCardView.swift index a088b48ac1..a440bb1f53 100644 --- a/iMEGA/Media Consumption/Photo Library/Card View/PhotoLibraryModeCardView.swift +++ b/iMEGA/Media Consumption/Photo Library/Card View/PhotoLibraryModeCardView.swift @@ -2,7 +2,7 @@ import Foundation import MEGASwiftUI import SwiftUI -struct PhotoLibraryModeCardView: View where Category: PhotoChronologicalCategory, VM: PhotoLibraryModeCardViewModel, Content: View, Content: Equatable { +struct PhotoLibraryModeCardView: View where Category: PhotoChronologicalCategory, VM: PhotoLibraryModeCardViewModel, Content: View { private let cellBuilder: (Category) -> Content @ObservedObject var viewModel: VM @@ -39,7 +39,6 @@ struct PhotoLibraryModeCardView: View where Category: Pho } }, label: { cellBuilder(category) - .equatable() .frame(height: PhotoLibraryConstants.cardHeight) }) .id(category.position) diff --git a/iMEGA/Media Consumption/Photo Library/Data Model/PhotoLibraryViewMode.swift b/iMEGA/Media Consumption/Photo Library/Data Model/PhotoLibraryViewMode.swift index 4f18abca26..d14ef2d97f 100644 --- a/iMEGA/Media Consumption/Photo Library/Data Model/PhotoLibraryViewMode.swift +++ b/iMEGA/Media Consumption/Photo Library/Data Model/PhotoLibraryViewMode.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n enum PhotoLibraryViewMode: CaseIterable, Identifiable { case year diff --git a/iMEGA/Media Consumption/Photo Library/Day View/PhotoDayCard.swift b/iMEGA/Media Consumption/Photo Library/Day View/PhotoDayCard.swift index a418a9f80a..880d535010 100644 --- a/iMEGA/Media Consumption/Photo Library/Day View/PhotoDayCard.swift +++ b/iMEGA/Media Consumption/Photo Library/Day View/PhotoDayCard.swift @@ -14,9 +14,3 @@ struct PhotoDayCard: View { } } } - -extension PhotoDayCard: Equatable { - static func == (lhs: PhotoDayCard, rhs: PhotoDayCard) -> Bool { - true // we are taking over the update of the view - } -} diff --git a/iMEGA/Media Consumption/Photo Library/Day View/PhotoLibraryDayView.swift b/iMEGA/Media Consumption/Photo Library/Day View/PhotoLibraryDayView.swift index aa6d29f02d..592b9bd893 100644 --- a/iMEGA/Media Consumption/Photo Library/Day View/PhotoLibraryDayView.swift +++ b/iMEGA/Media Consumption/Photo Library/Day View/PhotoLibraryDayView.swift @@ -10,9 +10,3 @@ struct PhotoLibraryDayView: View { } } } - -extension PhotoLibraryDayView: Equatable { - static func == (lhs: PhotoLibraryDayView, rhs: PhotoLibraryDayView) -> Bool { - true // we are taking over the update of the view - } -} diff --git a/iMEGA/Media Consumption/Photo Library/Month View/PhotoLibraryMonthView.swift b/iMEGA/Media Consumption/Photo Library/Month View/PhotoLibraryMonthView.swift index 2c25263d36..a925c3fd19 100644 --- a/iMEGA/Media Consumption/Photo Library/Month View/PhotoLibraryMonthView.swift +++ b/iMEGA/Media Consumption/Photo Library/Month View/PhotoLibraryMonthView.swift @@ -10,9 +10,3 @@ struct PhotoLibraryMonthView: View { } } } - -extension PhotoLibraryMonthView: Equatable { - static func == (lhs: PhotoLibraryMonthView, rhs: PhotoLibraryMonthView) -> Bool { - true // we are taking over the update of the view - } -} diff --git a/iMEGA/Media Consumption/Photo Library/Month View/PhotoMonthCard.swift b/iMEGA/Media Consumption/Photo Library/Month View/PhotoMonthCard.swift index e4a2db0411..b52789c0d3 100644 --- a/iMEGA/Media Consumption/Photo Library/Month View/PhotoMonthCard.swift +++ b/iMEGA/Media Consumption/Photo Library/Month View/PhotoMonthCard.swift @@ -14,9 +14,3 @@ struct PhotoMonthCard: View { } } } - -extension PhotoMonthCard: Equatable { - static func == (lhs: PhotoMonthCard, rhs: PhotoMonthCard) -> Bool { - true // we are taking over the update of the view - } -} diff --git a/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentConfiguration.swift b/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentConfiguration.swift index 8473fa7319..ec5d02e47e 100644 --- a/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentConfiguration.swift +++ b/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentConfiguration.swift @@ -1,4 +1,3 @@ - struct PhotoLibraryContentConfiguration { let selectLimit: Int? let scaleFactor: PhotoLibraryZoomState.ScaleFactor? diff --git a/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentView.swift b/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentView.swift index 2b26bbe024..2b238b053c 100644 --- a/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentView.swift +++ b/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentView.swift @@ -66,19 +66,16 @@ struct PhotoLibraryContentView: View { viewModel: PhotoLibraryYearViewModel(libraryViewModel: viewModel), router: router ) - .equatable() case .month: PhotoLibraryMonthView( viewModel: PhotoLibraryMonthViewModel(libraryViewModel: viewModel), router: router ) - .equatable() case .day: PhotoLibraryDayView( viewModel: PhotoLibraryDayViewModel(libraryViewModel: viewModel), router: router ) - .equatable() case .all: EmptyView() } diff --git a/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentViewModel.swift b/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentViewModel.swift index 813b5e24dc..f32cf88406 100644 --- a/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentViewModel.swift +++ b/iMEGA/Media Consumption/Photo Library/PhotoLibraryContentViewModel.swift @@ -1,6 +1,7 @@ import Combine import Foundation import MEGADomain +import MEGASDKRepo import SwiftUI @objc final class PhotoLibraryContentViewModel: NSObject, ObservableObject { diff --git a/iMEGA/Media Consumption/Photo Library/PhotoLibraryProvider.swift b/iMEGA/Media Consumption/Photo Library/PhotoLibraryProvider.swift index 8291c0008c..5447a717a6 100644 --- a/iMEGA/Media Consumption/Photo Library/PhotoLibraryProvider.swift +++ b/iMEGA/Media Consumption/Photo Library/PhotoLibraryProvider.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n import SwiftUI @MainActor @@ -43,10 +44,8 @@ extension PhotoLibraryProvider { if count == 0 { message = Strings.Localizable.selectTitle - } else if count == 1 { - message = Strings.Localizable.oneItemSelected(count) } else { - message = Strings.Localizable.itemsSelected(count) + message = Strings.Localizable.General.Format.itemsSelected(count) } navigationItem.title = message diff --git a/iMEGA/Media Consumption/Photo Library/Year View/PhotoLibraryYearView.swift b/iMEGA/Media Consumption/Photo Library/Year View/PhotoLibraryYearView.swift index 08f2c0e355..1e177d27aa 100644 --- a/iMEGA/Media Consumption/Photo Library/Year View/PhotoLibraryYearView.swift +++ b/iMEGA/Media Consumption/Photo Library/Year View/PhotoLibraryYearView.swift @@ -10,9 +10,3 @@ struct PhotoLibraryYearView: View { } } } - -extension PhotoLibraryYearView: Equatable { - static func == (lhs: PhotoLibraryYearView, rhs: PhotoLibraryYearView) -> Bool { - true // we are taking over the update of the view - } -} diff --git a/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionDetailView.swift b/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionDetailView.swift index a670c81bec..2d22de9661 100644 --- a/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionDetailView.swift +++ b/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionDetailView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct SlideShowOptionDetailView: View { diff --git a/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionRouter.swift b/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionRouter.swift index b51220e239..f11f767b01 100644 --- a/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionRouter.swift +++ b/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionRouter.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation import SwiftUI diff --git a/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionViewModel.swift b/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionViewModel.swift index a3575c48a1..c195a31dd7 100644 --- a/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionViewModel.swift +++ b/iMEGA/Media Consumption/Slideshow Library/Options/SlideShowOptionViewModel.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n enum SlideShowOptionName { case none diff --git a/iMEGA/Media Consumption/Slideshow Library/Views/SlideShowViewController.swift b/iMEGA/Media Consumption/Slideshow Library/Views/SlideShowViewController.swift index ca3b026d38..da18d878b8 100644 --- a/iMEGA/Media Consumption/Slideshow Library/Views/SlideShowViewController.swift +++ b/iMEGA/Media Consumption/Slideshow Library/Views/SlideShowViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGAPresentation import UIKit diff --git a/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkNonHostView.swift b/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkNonHostView.swift index 3831ca68c2..9fbe263be3 100644 --- a/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkNonHostView.swift +++ b/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkNonHostView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import SwiftUI struct ChatRoomLinkNonHostView: View { diff --git a/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkView.swift b/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkView.swift index ef39df5d08..a806bd0595 100644 --- a/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkView.swift +++ b/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ChatRoomLinkView: View { diff --git a/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkViewModel.swift b/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkViewModel.swift index 389e647d1f..2b031ae767 100644 --- a/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkViewModel.swift +++ b/iMEGA/MeetingInfoScene/ChatRoomLink/ChatRoomLinkViewModel.swift @@ -1,6 +1,6 @@ - import Combine import MEGADomain +import MEGAL10n final class ChatRoomLinkViewModel: ObservableObject { private var chatLinkUseCase: any ChatLinkUseCaseProtocol diff --git a/iMEGA/MeetingInfoScene/ChatRoomNotifications/ChatRoomNotificationsView.swift b/iMEGA/MeetingInfoScene/ChatRoomNotifications/ChatRoomNotificationsView.swift index c1b126f2fc..39d43340b9 100644 --- a/iMEGA/MeetingInfoScene/ChatRoomNotifications/ChatRoomNotificationsView.swift +++ b/iMEGA/MeetingInfoScene/ChatRoomNotifications/ChatRoomNotificationsView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ChatRoomNotificationsView: View { diff --git a/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantView.swift b/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantView.swift index 6c8a6c1ed4..de9680f9ad 100644 --- a/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantView.swift +++ b/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ChatRoomParticipantView: View { diff --git a/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantViewModel.swift b/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantViewModel.swift index 683b1173fb..12cf79d212 100644 --- a/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantViewModel.swift +++ b/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import MEGARepo import MEGASDKRepo diff --git a/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantsListView.swift b/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantsListView.swift index ae6f20db13..3a23a95807 100644 --- a/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantsListView.swift +++ b/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomParticipantsListView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ChatRoomParticipantsListView: View { diff --git a/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomPrivilege/ChatRoomParticipantPrivilege.swift b/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomPrivilege/ChatRoomParticipantPrivilege.swift index 050bfe6a45..54755f5905 100644 --- a/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomPrivilege/ChatRoomParticipantPrivilege.swift +++ b/iMEGA/MeetingInfoScene/ChatRoomParticipants/ChatRoomPrivilege/ChatRoomParticipantPrivilege.swift @@ -1,3 +1,5 @@ +import MEGAL10n + enum ChatRoomParticipantPrivilege: String, CaseIterable { case unknown case removed diff --git a/iMEGA/MeetingInfoScene/ChatRoomParticipants/Views/AddParticipantsView.swift b/iMEGA/MeetingInfoScene/ChatRoomParticipants/Views/AddParticipantsView.swift index 4a73c69d49..276dcd6263 100644 --- a/iMEGA/MeetingInfoScene/ChatRoomParticipants/Views/AddParticipantsView.swift +++ b/iMEGA/MeetingInfoScene/ChatRoomParticipants/Views/AddParticipantsView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct AddParticipantsView: View { diff --git a/iMEGA/MeetingInfoScene/ChatRoomParticipants/Views/SeeMoreParticipantsView.swift b/iMEGA/MeetingInfoScene/ChatRoomParticipants/Views/SeeMoreParticipantsView.swift index 7554666e0a..c5380ef6be 100644 --- a/iMEGA/MeetingInfoScene/ChatRoomParticipants/Views/SeeMoreParticipantsView.swift +++ b/iMEGA/MeetingInfoScene/ChatRoomParticipants/Views/SeeMoreParticipantsView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct SeeMoreParticipantsView: View { diff --git a/iMEGA/MeetingInfoScene/MeetingInfoRouter.swift b/iMEGA/MeetingInfoScene/MeetingInfoRouter.swift index 633a132bd8..cb8ddb5c7a 100644 --- a/iMEGA/MeetingInfoScene/MeetingInfoRouter.swift +++ b/iMEGA/MeetingInfoScene/MeetingInfoRouter.swift @@ -1,6 +1,7 @@ import ChatRepo import Combine import MEGADomain +import MEGAL10n import MEGARepo import MEGASDKRepo @@ -25,7 +26,7 @@ final class MeetingInfoRouter: NSObject, MeetingInfoRouting { ) let userImageUseCase = UserImageUseCase( - userImageRepo: UserImageRepository(sdk: MEGASdkManager.sharedMEGASdk()), + userImageRepo: UserImageRepository(sdk: MEGASdk.shared), userStoreRepo: UserStoreRepository(store: MEGAStore.shareInstance()), thumbnailRepo: ThumbnailRepository.newRepo, fileSystemRepo: FileSystemRepository.newRepo @@ -39,8 +40,8 @@ final class MeetingInfoRouter: NSObject, MeetingInfoRouting { userImageUseCase: userImageUseCase, chatUseCase: ChatUseCase( chatRepo: ChatRepository( - sdk: MEGASdkManager.sharedMEGASdk(), - chatSDK: MEGASdkManager.sharedMEGAChatSdk()) + sdk: MEGASdk.shared, + chatSDK: MEGAChatSdk.shared) ), accountUseCase: AccountUseCase(repository: AccountRepository.newRepo), chatLinkUseCase: ChatLinkUseCase(chatLinkRepository: ChatLinkRepository.newRepo), diff --git a/iMEGA/MeetingInfoScene/MeetingInfoView.swift b/iMEGA/MeetingInfoScene/MeetingInfoView.swift index 05060145bf..a7aed2d8d2 100644 --- a/iMEGA/MeetingInfoScene/MeetingInfoView.swift +++ b/iMEGA/MeetingInfoScene/MeetingInfoView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct MeetingInfoView: View { diff --git a/iMEGA/MeetingInfoScene/MeetingInfoViewController.swift b/iMEGA/MeetingInfoScene/MeetingInfoViewController.swift index 0e1f9772ee..3bc42c1fe7 100644 --- a/iMEGA/MeetingInfoScene/MeetingInfoViewController.swift +++ b/iMEGA/MeetingInfoScene/MeetingInfoViewController.swift @@ -1,4 +1,5 @@ import Combine +import MEGAL10n import MEGAPresentation import SwiftUI diff --git a/iMEGA/MeetingInfoScene/ScheduledMeetingDateBuilder.swift b/iMEGA/MeetingInfoScene/ScheduledMeetingDateBuilder.swift index cce93de7b7..21d38433e5 100644 --- a/iMEGA/MeetingInfoScene/ScheduledMeetingDateBuilder.swift +++ b/iMEGA/MeetingInfoScene/ScheduledMeetingDateBuilder.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import RegexBuilder struct ScheduledMeetingDateBuilder { @@ -79,14 +80,14 @@ struct ScheduledMeetingDateBuilder { private func dailyDateString(_ startDateString: String, _ endDateString: String, _ startTimeString: String, _ endTimeString: String) -> String { if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.daily.until", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.daily.until", comment: "") return String(format: format, scheduledMeeting.rules.interval == 0 ? 1 : scheduledMeeting.rules.interval) .replacingOccurrences(of: "[StartDate]", with: startDateString) .replacingOccurrences(of: "[UntilDate]", with: endDateString) .replacingOccurrences(of: "[StartTime]", with: startTimeString) .replacingOccurrences(of: "[EndTime]", with: endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.daily.forever", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.daily.forever", comment: "") return String(format: format, scheduledMeeting.rules.interval == 0 ? 1 : scheduledMeeting.rules.interval) .replacingOccurrences(of: "[StartDate]", with: startDateString) .replacingOccurrences(of: "[StartTime]", with: startTimeString) @@ -104,7 +105,7 @@ struct ScheduledMeetingDateBuilder { return dailyDateString(startDateString, endDateString, startTimeString, endTimeString) } else if weekDaysList.count == 1 { if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.weekly.oneDay.until", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.weekly.oneDay.until", comment: "") return String(format: format, scheduledMeeting.rules.interval == 0 ? 1 : scheduledMeeting.rules.interval) .replacingOccurrences(of: "[WeekDay]", with: stringRepresentingWeekDayShortName(forNumber: weekDaysList.first)) .replacingOccurrences(of: "[StartDate]", with: startDateString) @@ -112,7 +113,7 @@ struct ScheduledMeetingDateBuilder { .replacingOccurrences(of: "[StartTime]", with: startTimeString) .replacingOccurrences(of: "[EndTime]", with: endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.weekly.oneDay.forever", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.weekly.oneDay.forever", comment: "") return String(format: format, scheduledMeeting.rules.interval == 0 ? 1 : scheduledMeeting.rules.interval) .replacingOccurrences(of: "[WeekDay]", with: stringRepresentingWeekDayShortName(forNumber: weekDaysList.first)) .replacingOccurrences(of: "[StartDate]", with: startDateString) @@ -129,7 +130,7 @@ struct ScheduledMeetingDateBuilder { .joined(separator: ", ") let lastWeekdayString = stringRepresentingWeekDayShortName(forNumber: weekDaysList.last, isAtTheStartOfSentence: false) if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.weekly.severalDays.until", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.weekly.severalDays.until", comment: "") return String(format: format, scheduledMeeting.rules.interval == 0 ? 1 : scheduledMeeting.rules.interval) .replacingOccurrences(of: "[WeekDaysList]", with: weekdayStringList) .replacingOccurrences(of: "[LastWeekDay]", with: lastWeekdayString) @@ -138,7 +139,7 @@ struct ScheduledMeetingDateBuilder { .replacingOccurrences(of: "[StartTime]", with: startTimeString) .replacingOccurrences(of: "[EndTime]", with: endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.weekly.severalDays.forever", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.weekly.severalDays.forever", comment: "") return String(format: format, scheduledMeeting.rules.interval == 0 ? 1 : scheduledMeeting.rules.interval) .replacingOccurrences(of: "[WeekDaysList]", with: weekdayStringList) .replacingOccurrences(of: "[LastWeekDay]", with: lastWeekdayString) @@ -151,7 +152,7 @@ struct ScheduledMeetingDateBuilder { private func monthlySingleDayDateString(_ dayOfTheMonth: Int, _ startDateString: String, _ endDateString: String, _ startTimeString: String, _ endTimeString: String) -> String { if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.singleDay.until", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.singleDay.until", comment: "") return String(format: format, scheduledMeeting.rules.interval == 0 ? 1 : scheduledMeeting.rules.interval) .replacingOccurrences(of: "[MonthDay]", with: String(dayOfTheMonth)) .replacingOccurrences(of: "[StartDate]", with: startDateString) @@ -159,7 +160,7 @@ struct ScheduledMeetingDateBuilder { .replacingOccurrences(of: "[StartTime]", with: startTimeString) .replacingOccurrences(of: "[EndTime]", with: endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.singleDay.forever", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.singleDay.forever", comment: "") return String(format: format, scheduledMeeting.rules.interval == 0 ? 1 : scheduledMeeting.rules.interval) .replacingOccurrences(of: "[MonthDay]", with: String(dayOfTheMonth)) .replacingOccurrences(of: "[StartDate]", with: startDateString) @@ -172,42 +173,42 @@ struct ScheduledMeetingDateBuilder { switch weekOfMonth { case .first: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.monday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.monday.first", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.first", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .second: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.monday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.monday.second", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.second", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .third: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.monday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.monday.third", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.third", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fourth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.monday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.monday.fourth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.fourth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fifth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.monday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.monday.fifth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.monday.fifth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } } @@ -217,42 +218,42 @@ struct ScheduledMeetingDateBuilder { switch weekOfMonth { case .first: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.first", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.first", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .second: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.second", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.second", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .third: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.third", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.third", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fourth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.fourth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.fourth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fifth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.tuesday.fifth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.tuesday.fifth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } } @@ -262,42 +263,42 @@ struct ScheduledMeetingDateBuilder { switch weekOfMonth { case .first: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.first", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.first", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .second: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.second", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.second", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .third: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.third", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.third", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fourth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.fourth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.fourth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fifth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.wednesday.fifth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.wednesday.fifth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } } @@ -307,42 +308,42 @@ struct ScheduledMeetingDateBuilder { switch weekOfMonth { case .first: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.first", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.first", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .second: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.second", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.second", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .third: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.third", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.third", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fourth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.fourth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.fourth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fifth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.thursday.fifth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.thursday.fifth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } } @@ -352,42 +353,42 @@ struct ScheduledMeetingDateBuilder { switch weekOfMonth { case .first: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.friday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.friday.first", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.first", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .second: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.friday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.friday.second", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.second", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .third: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.friday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.friday.third", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.third", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fourth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.friday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.friday.fourth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.fourth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fifth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.friday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.friday.fifth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.friday.fifth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } } @@ -397,42 +398,42 @@ struct ScheduledMeetingDateBuilder { switch weekOfMonth { case .first: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.first", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.first", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .second: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.second", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.second", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .third: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.third", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.third", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fourth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.fourth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.fourth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fifth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.saturday.fifth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.saturday.fifth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } } @@ -442,42 +443,42 @@ struct ScheduledMeetingDateBuilder { switch weekOfMonth { case .first: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.first", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.first", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.first", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .second: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.second", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.second", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.second", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .third: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.third", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.third", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.third", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fourth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.fourth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.fourth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.fourth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } case .fifth: if scheduledMeeting.rules.until != nil { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.until.sunday.fifth", comment: "") return monthlyUntilDateString(format, startDateString, endDateString, startTimeString, endTimeString) } else { - let format = NSLocalizedString("meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.fifth", comment: "") + let format = Strings.localized("meetings.scheduled.recurring.monthly.ordinalDay.forever.sunday.fifth", comment: "") return monthlyForeverDateString(format, startDateString, startTimeString, endTimeString) } } diff --git a/iMEGA/MeetingInfoScene/Views/MeetingDescriptionView.swift b/iMEGA/MeetingInfoScene/Views/MeetingDescriptionView.swift index b555eacb96..3e6b8ba1f5 100644 --- a/iMEGA/MeetingInfoScene/Views/MeetingDescriptionView.swift +++ b/iMEGA/MeetingInfoScene/Views/MeetingDescriptionView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct MeetingDescriptionView: View { diff --git a/iMEGA/MeetingInfoScene/Views/MeetingInfoWaitingRoomSettingView.swift b/iMEGA/MeetingInfoScene/Views/MeetingInfoWaitingRoomSettingView.swift index 4b0356ad3b..d4094aefc6 100644 --- a/iMEGA/MeetingInfoScene/Views/MeetingInfoWaitingRoomSettingView.swift +++ b/iMEGA/MeetingInfoScene/Views/MeetingInfoWaitingRoomSettingView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct MeetingInfoWaitingRoomSettingView: View { diff --git a/iMEGA/MeetingInfoScene/Views/WaitingRoomWarningBannerView.swift b/iMEGA/MeetingInfoScene/Views/WaitingRoomWarningBannerView.swift index 11869051fe..e6b21b7986 100644 --- a/iMEGA/MeetingInfoScene/Views/WaitingRoomWarningBannerView.swift +++ b/iMEGA/MeetingInfoScene/Views/WaitingRoomWarningBannerView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct WaitingRoomWarningBannerView: View { @@ -8,9 +9,7 @@ struct WaitingRoomWarningBannerView: View { var body: some View { HStack { - if let url = URL(string: "https://help.mega.io/wp-admin/post.php?post=3005&action=edit") { - buildBannerText(with: url) - } + text Spacer() closeButton } @@ -18,26 +17,13 @@ struct WaitingRoomWarningBannerView: View { .background(colorScheme == .dark ? Color(Colors.General.Yellow.fed42926.name) : Color(Colors.General.Yellow.fed429.name)) } - private func buildBannerText(with url: URL) -> some View { - Link(destination: url) { - Group { - buildText(with: Strings.Localizable.Meetings.ScheduleMeeting.WaitingRoomWarningBanner.title) - + buildText(with: " ") - + buildText(with: Strings.Localizable.Meetings.ScheduleMeeting.WaitingRoomWarningBanner.learnMore) - .underline() - } - .multilineTextAlignment(.leading) - } - } - - private func buildText(with title: String) -> Text { - Text(title) - .font(.caption2) - .bold() + private var text: some View { + Text(Strings.Localizable.Meetings.ScheduleMeeting.WaitingRoomWarningBanner.title) + .font(.caption2.bold()) .foregroundColor(colorScheme == .dark ? Color(Colors.General.Yellow.ffd60A.name): Color(Colors.General.Yellow._9D8319.name)) } - var closeButton: some View { + private var closeButton: some View { Button { withAnimation { showBanner = false diff --git a/iMEGA/My Account/Account Hall/MyAccountHallPlanView.swift b/iMEGA/My Account/Account Hall/MyAccountHallPlanView.swift index 56253cb07d..fe02f3de75 100644 --- a/iMEGA/My Account/Account Hall/MyAccountHallPlanView.swift +++ b/iMEGA/My Account/Account Hall/MyAccountHallPlanView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct MyAccountHallPlanView: View { diff --git a/iMEGA/My Account/Account Hall/MyAccountHallTableViewCell.h b/iMEGA/My Account/Account Hall/MyAccountHallTableViewCell.h index 66bf5584e4..698cd26547 100644 --- a/iMEGA/My Account/Account Hall/MyAccountHallTableViewCell.h +++ b/iMEGA/My Account/Account Hall/MyAccountHallTableViewCell.h @@ -1,4 +1,3 @@ - @interface MyAccountHallTableViewCell : UITableViewCell @property (weak, nonatomic) IBOutlet UILabel *sectionLabel; diff --git a/iMEGA/My Account/Account Hall/MyAccountHallTableViewCell.m b/iMEGA/My Account/Account Hall/MyAccountHallTableViewCell.m index e911dc6dab..2aac51809e 100644 --- a/iMEGA/My Account/Account Hall/MyAccountHallTableViewCell.m +++ b/iMEGA/My Account/Account Hall/MyAccountHallTableViewCell.m @@ -1,4 +1,3 @@ - #import "MyAccountHallTableViewCell.h" #import "MEGA-Swift.h" @@ -33,4 +32,12 @@ - (void)setupCell { } } +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + + if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) { + [self setupCell]; + } +} + @end diff --git a/iMEGA/My Account/Account Hall/MyAccountHallViewController+Addition.swift b/iMEGA/My Account/Account Hall/MyAccountHallViewController+Addition.swift index 1294d51b42..b48a060e11 100644 --- a/iMEGA/My Account/Account Hall/MyAccountHallViewController+Addition.swift +++ b/iMEGA/My Account/Account Hall/MyAccountHallViewController+Addition.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n import MEGASwiftUI extension MyAccountHallViewController { diff --git a/iMEGA/My Account/Account Hall/MyAccountHallViewController+TableViewDataSource.swift b/iMEGA/My Account/Account Hall/MyAccountHallViewController+TableViewDataSource.swift index 61a1500e49..d9612be779 100644 --- a/iMEGA/My Account/Account Hall/MyAccountHallViewController+TableViewDataSource.swift +++ b/iMEGA/My Account/Account Hall/MyAccountHallViewController+TableViewDataSource.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGASwiftUI @objc enum MyAccountSection: Int, CaseIterable { @@ -22,7 +23,7 @@ extension MyAccountHallViewController: UITableViewDataSource { // MARK: - Storage row setup data for Business and Pro Flexi accounts private func storageBusinessAccountSetupData() -> MyAccountHallCellData { - let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails + let accountDetails = MEGASdk.shared.mnz_accountDetails return MyAccountHallCellData(sectionText: Strings.Localizable.storage, storageText: Strings.Localizable.storage, transferText: Strings.Localizable.transfer, @@ -32,7 +33,7 @@ extension MyAccountHallViewController: UITableViewDataSource { // MARK: - Storage row setup data private func storageSetupData() -> MyAccountHallCellData { - let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails + let accountDetails = MEGASdk.shared.mnz_accountDetails let detailText = String(format: "%@ / %@", NSString.mnz_formatString(fromByteCountFormatter: String.memoryStyleString(fromByteCount: accountDetails?.storageUsed.int64Value ?? 0)), NSString.mnz_formatString(fromByteCountFormatter: String.memoryStyleString(fromByteCount: accountDetails?.storageMax.int64Value ?? 0))) @@ -114,8 +115,8 @@ extension MyAccountHallViewController: UITableViewDataSource { // MARK: - Rubbish Bin row setup data private func rubbishBinSetupData() -> MyAccountHallCellData { var rubbishBinSize = "" - if let rubbishBinNode = MEGASdkManager.sharedMEGASdk().rubbishNode { - rubbishBinSize = NSString.mnz_formatString(fromByteCountFormatter: Helper.size(for: rubbishBinNode, api: MEGASdkManager.sharedMEGASdk())) + if let rubbishBinNode = MEGASdk.shared.rubbishNode { + rubbishBinSize = NSString.mnz_formatString(fromByteCountFormatter: Helper.size(for: rubbishBinNode, api: MEGASdk.shared)) } return MyAccountHallCellData(sectionText: Strings.Localizable.rubbishBinLabel, diff --git a/iMEGA/My Account/Account Hall/MyAccountHallViewController+TableViewDelegate.swift b/iMEGA/My Account/Account Hall/MyAccountHallViewController+TableViewDelegate.swift index b53302dc11..74a800cb4d 100644 --- a/iMEGA/My Account/Account Hall/MyAccountHallViewController+TableViewDelegate.swift +++ b/iMEGA/My Account/Account Hall/MyAccountHallViewController+TableViewDelegate.swift @@ -1,5 +1,6 @@ import DeviceCenter import MEGADomain +import MEGAL10n import MEGASDKRepo extension MyAccountHallViewController: UITableViewDelegate { @@ -205,7 +206,45 @@ extension MyAccountHallViewController: UITableViewDelegate { colorName: Colors.General.Red.ff3B30.name, iconName: Asset.Images.BackupStatus.disabled.name ) - ] + ], + deviceCenterActions: [ + DeviceCenterAction( + type: .cameraUploads, + title: Strings.Localizable.cameraUploadsLabel, + subtitle: "", + icon: Asset.Images.Settings.cameraUploadsSettings.name, + action: { + } + ), + DeviceCenterAction( + type: .info, + title: Strings.Localizable.info, + icon: Asset.Images.Generic.info.name, + action: { + } + ), + DeviceCenterAction( + type: .rename, + title: Strings.Localizable.rename, + icon: Asset.Images.Generic.rename.name, + action: { + } + ), + DeviceCenterAction( + type: .showInCD, + title: Strings.Localizable.Device.Center.Show.In.Cloud.Drive.Action.title, + icon: Asset.Images.ActionSheetIcons.cloudDriveFolder.name, + action: { + } + ), + DeviceCenterAction( + type: .showInBackups, + title: Strings.Localizable.Device.Center.Show.In.Backups.Action.title, + icon: Asset.Images.MyAccount.backups.name, + action: { + } + ) + ] ) } } diff --git a/iMEGA/My Account/Account Hall/MyAccountHallViewController.m b/iMEGA/My Account/Account Hall/MyAccountHallViewController.m index 0043cbe501..01eb96dc9d 100644 --- a/iMEGA/My Account/Account Hall/MyAccountHallViewController.m +++ b/iMEGA/My Account/Account Hall/MyAccountHallViewController.m @@ -1,4 +1,3 @@ - #import "MyAccountHallViewController.h" #import "AchievementsViewController.h" @@ -18,6 +17,7 @@ #import "UIImage+MNZCategory.h" #import "UsageViewController.h" +@import MEGAL10nObjc; @import MEGASDKRepo; @interface MyAccountHallViewController () @@ -46,8 +46,8 @@ @implementation MyAccountHallViewController - (void)viewDidLoad { [super viewDidLoad]; - self.viewAndEditProfileLabel.text = NSLocalizedString(@"viewAndEditProfile", @"Title show on the hall of My Account section that describes a place where you can view, edit and upgrade your account and profile"); - self.viewAndEditProfileButton.accessibilityLabel = NSLocalizedString(@"viewAndEditProfile", @"Title show on the hall of My Account section that describes a place where you can view, edit and upgrade your account and profile"); + self.viewAndEditProfileLabel.text = LocalizedString(@"viewAndEditProfile", @"Title show on the hall of My Account section that describes a place where you can view, edit and upgrade your account and profile"); + self.viewAndEditProfileButton.accessibilityLabel = LocalizedString(@"viewAndEditProfile", @"Title show on the hall of My Account section that describes a place where you can view, edit and upgrade your account and profile"); [self registerCustomCells]; @@ -152,7 +152,7 @@ - (void)updateAppearance { self.tableFooterLabel.textColor = [UIColor mnz_subtitlesForTraitCollection:self.traitCollection]; } - [self setMenuCapableBackButtonWithMenuTitle:NSLocalizedString(@"My Account", nil)]; + [self setMenuCapableBackButtonWithMenuTitle:LocalizedString(@"My Account", @"")]; [self.navigationItem.rightBarButtonItem setTitleTextAttributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]} forState:UIControlStateNormal]; @@ -161,17 +161,17 @@ - (void)updateAppearance { } - (void)configAddPhoneNumberTexts { - self.addPhoneNumberTitle.text = NSLocalizedString(@"Add Your Phone Number", nil); + self.addPhoneNumberTitle.text = LocalizedString(@"Add Your Phone Number", @""); if (!MEGASdkManager.sharedMEGASdk.isAchievementsEnabled) { - self.addPhoneNumberDescription.text = NSLocalizedString(@"Add your phone number to MEGA. This makes it easier for your contacts to find you on MEGA.", nil); + self.addPhoneNumberDescription.text = LocalizedString(@"Add your phone number to MEGA. This makes it easier for your contacts to find you on MEGA.", @""); } else { [self.addPhoneNumberActivityIndicator startAnimating]; [MEGASdkManager.sharedMEGASdk getAccountAchievementsWithDelegate:[[MEGAGenericRequestDelegate alloc] initWithCompletion:^(MEGARequest * _Nonnull request, MEGAError * _Nonnull error) { [self.addPhoneNumberActivityIndicator stopAnimating]; if (error.type == MEGAErrorTypeApiOk) { NSString *storageText = [NSString memoryStyleStringFromByteCount:[request.megaAchievementsDetails classStorageForClassId:MEGAAchievementAddPhone]]; - self.addPhoneNumberDescription.text = [NSString stringWithFormat:NSLocalizedString(@"Get free %@ when you add your phone number. This makes it easier for your contacts to find you on MEGA.", nil), storageText]; + self.addPhoneNumberDescription.text = [NSString stringWithFormat:LocalizedString(@"Get free %@ when you add your phone number. This makes it easier for your contacts to find you on MEGA.", @""), storageText]; } }]]; } diff --git a/iMEGA/My Account/Account Hall/ViewModel/AccountHallViewModel.swift b/iMEGA/My Account/Account Hall/ViewModel/AccountHallViewModel.swift index 8c9e56297e..3e850a21f5 100644 --- a/iMEGA/My Account/Account Hall/ViewModel/AccountHallViewModel.swift +++ b/iMEGA/My Account/Account Hall/ViewModel/AccountHallViewModel.swift @@ -3,6 +3,7 @@ import Foundation import MEGADomain import MEGAPresentation import MEGASDKRepo +import MEGASwift enum AccountHallLoadTarget { case planList, accountDetails, contentCounts @@ -25,15 +26,16 @@ final class AccountHallViewModel: ViewModelType, ObservableObject { case setUserAvatar case setName } - + + @Atomic var relevantUnseenUserAlertsCount: UInt = 0 + @Atomic var isNewUpgradeAccountPlanEnabled: Bool = false + @Atomic var accountDetails: AccountDetailsEntity? + var invokeCommand: ((Command) -> Void)? var incomingContactRequestsCount = 0 - var relevantUnseenUserAlertsCount: UInt = 0 var setupABTestVariantTask: Task? - var isNewUpgradeAccountPlanEnabled: Bool = false - + private(set) var planList: [AccountPlanEntity] = [] - private(set) var accountDetails: AccountDetailsEntity? private var featureFlagProvider: any FeatureFlagProviderProtocol private var abTestProvider: any ABTestProviderProtocol private let accountHallUsecase: any AccountHallUseCaseProtocol @@ -74,7 +76,10 @@ final class AccountHallViewModel: ViewModelType, ObservableObject { func setupABTestVariant() { setupABTestVariantTask = Task { [weak self] in guard let self else { return } - isNewUpgradeAccountPlanEnabled = await abTestProvider.abTestVariant(for: .upgradePlanRevamp) == .variantA + let isNewUpgradeAccountPlanEnabled = await abTestProvider.abTestVariant(for: .upgradePlanRevamp) == .variantA + $isNewUpgradeAccountPlanEnabled.mutate { currentValue in + currentValue = isNewUpgradeAccountPlanEnabled + } } } @@ -121,7 +126,9 @@ final class AccountHallViewModel: ViewModelType, ObservableObject { // MARK: - Private private func setAccountDetails(_ details: AccountDetailsEntity?) { - accountDetails = details + $accountDetails.mutate { currentValue in + currentValue = details + } Task { @MainActor in currentPlanName = details?.proLevel.toAccountTypeDisplayName() ?? "" } @@ -160,7 +167,10 @@ final class AccountHallViewModel: ViewModelType, ObservableObject { private func fetchCounts() async { incomingContactRequestsCount = await accountHallUsecase.incomingContactsRequestsCount() - relevantUnseenUserAlertsCount = await accountHallUsecase.relevantUnseenUserAlertsCount() + let relevantUnseenUserAlertsCount = await accountHallUsecase.relevantUnseenUserAlertsCount() + $relevantUnseenUserAlertsCount.mutate { currentValue in + currentValue = relevantUnseenUserAlertsCount + } await reloadNotificationCounts() } @@ -223,7 +233,9 @@ final class AccountHallViewModel: ViewModelType, ObservableObject { .receive(on: DispatchQueue.main) .sink { [weak self] newCount in guard let self else { return } - relevantUnseenUserAlertsCount = newCount + $relevantUnseenUserAlertsCount.mutate { currentValue in + currentValue = newCount + } invokeCommand?(.reloadCounts) } .store(in: &subscriptions) @@ -231,17 +243,25 @@ final class AccountHallViewModel: ViewModelType, ObservableObject { NotificationCenter .default .publisher(for: .refreshAccountDetails) - .sink { [weak self] _ in + .map({ $0.object as? AccountDetailsEntity }) + .sink { [weak self] account in guard let self else { return } Task { [weak self] in guard let self else { return } - await fetchAccountDetails(showActivityIndicator: true) + + guard let account else { + await fetchAccountDetails(showActivityIndicator: true) + return + } + + setAccountDetails(account) + await configPlanDisplay() } } .store(in: &subscriptions) } - private func handleRequestResult(_ result: Result) { + private func handleRequestResult(_ result: Result) { if case .success(let request) = result { switch request.type { case .accountDetails: diff --git a/iMEGA/My Account/Achievements/AchievementsDetailsViewController+Additions.swift b/iMEGA/My Account/Achievements/AchievementsDetailsViewController+Additions.swift index 79e5d57570..e0b4c7dad9 100644 --- a/iMEGA/My Account/Achievements/AchievementsDetailsViewController+Additions.swift +++ b/iMEGA/My Account/Achievements/AchievementsDetailsViewController+Additions.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGASDKRepo extension AchievementsDetailsViewController { diff --git a/iMEGA/My Account/Achievements/AchievementsDetailsViewController.h b/iMEGA/My Account/Achievements/AchievementsDetailsViewController.h index 8a40bd0280..1f6ad2e8f2 100644 --- a/iMEGA/My Account/Achievements/AchievementsDetailsViewController.h +++ b/iMEGA/My Account/Achievements/AchievementsDetailsViewController.h @@ -1,4 +1,3 @@ - #import @interface AchievementsDetailsViewController : UIViewController diff --git a/iMEGA/My Account/Achievements/AchievementsDetailsViewController.m b/iMEGA/My Account/Achievements/AchievementsDetailsViewController.m index 4f10e2952e..2386c54368 100644 --- a/iMEGA/My Account/Achievements/AchievementsDetailsViewController.m +++ b/iMEGA/My Account/Achievements/AchievementsDetailsViewController.m @@ -1,9 +1,10 @@ - #import "AchievementsDetailsViewController.h" #import "Helper.h" #import "MEGA-Swift.h" #import "NSString+MNZCategory.h" +@import MEGAL10nObjc; + @interface AchievementsDetailsViewController () @property (weak, nonatomic) IBOutlet UIImageView *checkImageView; @@ -48,19 +49,19 @@ - (void)setupTitleImage { UIImage *achievementImage; switch (self.achievementClass) { case MEGAAchievementWelcome: { - self.navigationItem.title = NSLocalizedString(@"account.achievement.registration.title", nil); + self.navigationItem.title = LocalizedString(@"account.achievement.registration.title", @""); achievementImage = [UIImage imageNamed:@"achievementsRegistration"]; break; } case MEGAAchievementDesktopInstall: { - self.navigationItem.title = NSLocalizedString(@"account.achievement.desktopApp.title", nil); + self.navigationItem.title = LocalizedString(@"account.achievement.desktopApp.title", @""); achievementImage = [UIImage imageNamed:@"achievementsInstallMega"]; break; } case MEGAAchievementMobileInstall: { - self.navigationItem.title = NSLocalizedString(@"account.achievement.mobileApp.title", nil); + self.navigationItem.title = LocalizedString(@"account.achievement.mobileApp.title", @""); achievementImage = [UIImage imageNamed:@"achievementsInstallMobile"]; break; } diff --git a/iMEGA/My Account/Achievements/AchievementsTableViewCell.h b/iMEGA/My Account/Achievements/AchievementsTableViewCell.h index 00df658052..7f8d2c4c6b 100644 --- a/iMEGA/My Account/Achievements/AchievementsTableViewCell.h +++ b/iMEGA/My Account/Achievements/AchievementsTableViewCell.h @@ -1,4 +1,3 @@ - @interface AchievementsTableViewCell : UITableViewCell @property (weak, nonatomic) IBOutlet UIView *storageQuotaRewardView; diff --git a/iMEGA/My Account/Achievements/AchievementsTableViewCell.m b/iMEGA/My Account/Achievements/AchievementsTableViewCell.m index 563e632dc3..befd6f6992 100644 --- a/iMEGA/My Account/Achievements/AchievementsTableViewCell.m +++ b/iMEGA/My Account/Achievements/AchievementsTableViewCell.m @@ -1,4 +1,3 @@ - #import "AchievementsTableViewCell.h" @implementation AchievementsTableViewCell diff --git a/iMEGA/My Account/Achievements/AchievementsViewController+Additions.swift b/iMEGA/My Account/Achievements/AchievementsViewController+Additions.swift index 82b2919126..ddd2e8b299 100644 --- a/iMEGA/My Account/Achievements/AchievementsViewController+Additions.swift +++ b/iMEGA/My Account/Achievements/AchievementsViewController+Additions.swift @@ -1,3 +1,5 @@ +import MEGAL10n + extension AchievementsViewController { open override func viewDidLayoutSubviews() { diff --git a/iMEGA/My Account/Achievements/AchievementsViewController.h b/iMEGA/My Account/Achievements/AchievementsViewController.h index 20d4447c02..c8215bbca7 100644 --- a/iMEGA/My Account/Achievements/AchievementsViewController.h +++ b/iMEGA/My Account/Achievements/AchievementsViewController.h @@ -1,4 +1,3 @@ - #import @interface AchievementsViewController : UIViewController diff --git a/iMEGA/My Account/Achievements/AchievementsViewController.m b/iMEGA/My Account/Achievements/AchievementsViewController.m index 549092d586..50f424b812 100644 --- a/iMEGA/My Account/Achievements/AchievementsViewController.m +++ b/iMEGA/My Account/Achievements/AchievementsViewController.m @@ -1,4 +1,3 @@ - #import "AchievementsViewController.h" #import "Helper.h" #import "MEGASdkManager.h" @@ -12,6 +11,7 @@ #import "NSArray+MNZCategory.h" @import MEGAUIKit; +@import MEGAL10nObjc; @interface AchievementsViewController () @@ -51,22 +51,22 @@ - (void)viewDidLoad { [self.tableView sizeHeaderToFit]; - self.navigationItem.title = NSLocalizedString(@"achievementsTitle", @"Title of the Achievements section"); + self.navigationItem.title = LocalizedString(@"achievementsTitle", @"Title of the Achievements section"); [self configureBackButton]; - self.inviteYourFriendsTitleLabel.text = NSLocalizedString(@"account.achievement.referral.title", nil); + self.inviteYourFriendsTitleLabel.text = LocalizedString(@"account.achievement.referral.title", @""); UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(inviteYourFriendsTapped)]; self.inviteYourFriendsView.gestureRecognizers = @[tapGestureRecognizer]; self.disclosureIndicatorImageView.image = self.disclosureIndicatorImageView.image.imageFlippedForRightToLeftLayoutDirection; - self.unlockedBonusesLabel.text = NSLocalizedString(@"unlockedBonuses", @"Header of block with achievements bonuses."); - self.storageQuotaLabel.text = NSLocalizedString(@"storageQuota", @"A header/title of a section which contains information about used/available storage space on a user's cloud drive."); + self.unlockedBonusesLabel.text = LocalizedString(@"unlockedBonuses", @"Header of block with achievements bonuses."); + self.storageQuotaLabel.text = LocalizedString(@"storageQuota", @"A header/title of a section which contains information about used/available storage space on a user's cloud drive."); [[MEGASdkManager sharedMEGASdk] getAccountAchievementsWithDelegate:self]; if (self.enableCloseBarButton) { //For modal presentations - UIBarButtonItem *rightButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"skipButton", @"Button title that skips the current action") + UIBarButtonItem *rightButtonItem = [[UIBarButtonItem alloc] initWithTitle:LocalizedString(@"skipButton", @"Button title that skips the current action") style:UIBarButtonItemStyleDone target:self action:@selector(dismissViewController)]; @@ -145,7 +145,7 @@ - (void)setStorageQuotaRewardsForCell:(AchievementsTableViewCell *)cell forIndex } cell.storageQuotaRewardView.backgroundColor = cell.storageQuotaRewardLabel.backgroundColor = ((classStorageReward == 0) ? [UIColor mnz_tertiaryGrayForTraitCollection:self.traitCollection] : [UIColor mnz_blueForTraitCollection:self.traitCollection]); - cell.storageQuotaRewardLabel.text = (classStorageReward == 0) ? NSLocalizedString(@"— GB", nil) : [NSString memoryStyleStringFromByteCount:classStorageReward]; + cell.storageQuotaRewardLabel.text = (classStorageReward == 0) ? LocalizedString(@"— GB", @"") : [NSString memoryStyleStringFromByteCount:classStorageReward]; } - (void)pushAchievementsDetailsWithIndexPath:(NSIndexPath *)indexPath achievementClass:(MEGAAchievement)achievementClass { @@ -205,7 +205,7 @@ - (void)setupView:(MEGAAchievementsDetails *)achievementDetails { ]]; NSString *inviteStorageString = [NSString memoryStyleStringFromByteCount:[self.achievementsDetails classStorageForClassId:MEGAAchievementInvite]]; - self.inviteYourFriendsSubtitleLabel.text = [NSString stringWithFormat:NSLocalizedString(@"account.achievement.referral.subtitle", nil), inviteStorageString]; + self.inviteYourFriendsSubtitleLabel.text = [NSString stringWithFormat:LocalizedString(@"account.achievement.referral.subtitle", @""), inviteStorageString]; self.unlockedStorageQuotaLabel.attributedText = [self textForUnlockedBonuses:self.achievementsDetails.currentStorage]; @@ -227,22 +227,22 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N switch (achievementClass) { case MEGAAchievementInvite: { - cell.titleLabel.text = NSLocalizedString(@"account.achievement.referralBonus.title", nil); + cell.titleLabel.text = LocalizedString(@"account.achievement.referralBonus.title", @""); break; } case MEGAAchievementWelcome: { - cell.titleLabel.text = NSLocalizedString(@"account.achievement.registration.title", nil); + cell.titleLabel.text = LocalizedString(@"account.achievement.registration.title", @""); break; } case MEGAAchievementDesktopInstall: { - cell.titleLabel.text = NSLocalizedString(@"account.achievement.desktopApp.title", nil); + cell.titleLabel.text = LocalizedString(@"account.achievement.desktopApp.title", @""); break; } case MEGAAchievementMobileInstall: { - cell.titleLabel.text = NSLocalizedString(@"account.achievement.mobileApp.title", nil); + cell.titleLabel.text = LocalizedString(@"account.achievement.mobileApp.title", @""); break; } @@ -267,7 +267,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.storageQuotaRewardLabel.text = storageString; cell.storageQuotaRewardView.backgroundColor = cell.storageQuotaRewardLabel.backgroundColor = [UIColor mnz_blueForTraitCollection:self.traitCollection]; - cell.subtitleLabel.text = [NSString stringWithFormat:NSLocalizedString(@"account.achievement.incomplete.subtitle", nil), storageString]; + cell.subtitleLabel.text = [NSString stringWithFormat:LocalizedString(@"account.achievement.incomplete.subtitle", @""), storageString]; cell.subtitleLabel.textColor = [UIColor mnz_subtitlesForTraitCollection:self.traitCollection]; } } diff --git a/iMEGA/My Account/Achievements/InviteFriendsViewController.m b/iMEGA/My Account/Achievements/InviteFriendsViewController.m index 43ea157a65..5b46c3f31f 100644 --- a/iMEGA/My Account/Achievements/InviteFriendsViewController.m +++ b/iMEGA/My Account/Achievements/InviteFriendsViewController.m @@ -1,9 +1,10 @@ - #import "InviteFriendsViewController.h" #import "MEGA-Swift.h" #import "NSString+MNZCategory.h" +@import MEGAL10nObjc; + @interface InviteFriendsViewController () @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; @@ -31,15 +32,15 @@ - (void)viewDidLoad { [super viewDidLoad]; [self configureNavigationBar]; - self.inviteYourFriendsTitleLabel.text = NSLocalizedString(@"account.achievement.referral.title", nil); + self.inviteYourFriendsTitleLabel.text = LocalizedString(@"account.achievement.referral.title", @""); self.inviteYourFriendsSubtitleLabel.text = self.inviteYourFriendsSubtitleString; - [self.inviteButton setTitle:NSLocalizedString(@"invite", @"A button on a dialog which invites a contact to join MEGA.") forState:UIControlStateNormal]; + [self.inviteButton setTitle:LocalizedString(@"invite", @"A button on a dialog which invites a contact to join MEGA.") forState:UIControlStateNormal]; - self.howItWorksLabel.text = NSLocalizedString(@"howItWorks", @""); - self.howItWorksFirstParagraphLabel.text = [NSLocalizedString(@"howItWorksMain", @"") mnz_removeWebclientFormatters]; - self.howItWorksSecondParagraphLabel.text = NSLocalizedString(@"howItWorksSecondary", @""); - self.howItWorksThirdParagraphLabel.text = NSLocalizedString(@"howItWorksTertiary", @"A message which is shown once someone has invited a friend as part of the achievements program."); + self.howItWorksLabel.text = LocalizedString(@"howItWorks", @""); + self.howItWorksFirstParagraphLabel.text = [LocalizedString(@"howItWorksMain", @"") mnz_removeWebclientFormatters]; + self.howItWorksSecondParagraphLabel.text = LocalizedString(@"howItWorksSecondary", @""); + self.howItWorksThirdParagraphLabel.text = LocalizedString(@"howItWorksTertiary", @"A message which is shown once someone has invited a friend as part of the achievements program."); [self updateAppearance]; } diff --git a/iMEGA/My Account/Achievements/InviteYourFriendsViewController+Additions.swift b/iMEGA/My Account/Achievements/InviteYourFriendsViewController+Additions.swift index 7f9f70e8f1..ae746cd8d6 100644 --- a/iMEGA/My Account/Achievements/InviteYourFriendsViewController+Additions.swift +++ b/iMEGA/My Account/Achievements/InviteYourFriendsViewController+Additions.swift @@ -1,3 +1,5 @@ +import MEGAL10n + extension InviteFriendsViewController { @objc func configureNavigationBar() { diff --git a/iMEGA/My Account/Achievements/ReferralBonusesTableViewController+Additions.swift b/iMEGA/My Account/Achievements/ReferralBonusesTableViewController+Additions.swift index fa6890fd17..d1b72fbeab 100644 --- a/iMEGA/My Account/Achievements/ReferralBonusesTableViewController+Additions.swift +++ b/iMEGA/My Account/Achievements/ReferralBonusesTableViewController+Additions.swift @@ -1,3 +1,5 @@ +import MEGAL10n + extension ReferralBonusesTableViewController { @objc func awardDaysLeftMessage(_ remainingDays: Int) -> String { return Strings.Localizable.Account.Achievement.Complete.ValidDays.subtitle(remainingDays) diff --git a/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.h b/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.h index 486e57baff..d7b11ed59f 100644 --- a/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.h +++ b/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.h @@ -1,4 +1,3 @@ - #import @interface ReferralBonusesTableViewController : UITableViewController diff --git a/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.m b/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.m index 24364f9a1a..75955d5516 100644 --- a/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.m +++ b/iMEGA/My Account/Achievements/ReferralBonusesTableViewController.m @@ -1,4 +1,3 @@ - #import "ReferralBonusesTableViewController.h" #import "Helper.h" #import "MEGAGetAttrUserRequestDelegate.h" @@ -11,6 +10,8 @@ #import "AchievementsTableViewCell.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @interface ReferralBonusesTableViewController () @property (nonatomic) NSMutableArray *inviteAchievementsIndexesMutableArray; @@ -27,7 +28,7 @@ - (void)viewDidLoad { self.tableView.tableFooterView = [UIView.alloc initWithFrame:CGRectZero]; - self.navigationItem.title = NSLocalizedString(@"account.achievement.referralBonus.title", nil); + self.navigationItem.title = LocalizedString(@"account.achievement.referralBonus.title", @""); self.inviteAchievementsIndexesMutableArray = [[NSMutableArray alloc] init]; NSUInteger awardsCount = self.achievementsDetails.awardsCount; @@ -69,7 +70,7 @@ - (void)setStorageQuotaRewardsForCell:(AchievementsTableViewCell *)cell withAwar long long classStorageReward = [self.achievementsDetails rewardStorageByAwardId:awardId]; cell.storageQuotaRewardView.backgroundColor = cell.storageQuotaRewardLabel.backgroundColor = ((classStorageReward == 0) ? [UIColor mnz_tertiaryGrayForTraitCollection:self.traitCollection] : [UIColor mnz_blueForTraitCollection:self.traitCollection]); - cell.storageQuotaRewardLabel.text = (classStorageReward == 0) ? NSLocalizedString(@"— GB", nil) : [NSString memoryStyleStringFromByteCount:classStorageReward]; + cell.storageQuotaRewardLabel.text = (classStorageReward == 0) ? LocalizedString(@"— GB", @"") : [NSString memoryStyleStringFromByteCount:classStorageReward]; } #pragma mark - UITableViewDataSource diff --git a/iMEGA/My Account/Contacts/Cells/ContactRequestsTableViewCell.h b/iMEGA/My Account/Contacts/Cells/ContactRequestsTableViewCell.h index 4bcd29dd88..6780574630 100644 --- a/iMEGA/My Account/Contacts/Cells/ContactRequestsTableViewCell.h +++ b/iMEGA/My Account/Contacts/Cells/ContactRequestsTableViewCell.h @@ -1,4 +1,3 @@ - #import @interface ContactRequestsTableViewCell : UITableViewCell diff --git a/iMEGA/My Account/Contacts/Cells/ContactRequestsTableViewCell.m b/iMEGA/My Account/Contacts/Cells/ContactRequestsTableViewCell.m index 499fb77586..e3b13858e2 100644 --- a/iMEGA/My Account/Contacts/Cells/ContactRequestsTableViewCell.m +++ b/iMEGA/My Account/Contacts/Cells/ContactRequestsTableViewCell.m @@ -1,4 +1,3 @@ - #import "ContactRequestsTableViewCell.h" @implementation ContactRequestsTableViewCell diff --git a/iMEGA/My Account/Contacts/Cells/ContactTableViewCell.m b/iMEGA/My Account/Contacts/Cells/ContactTableViewCell.m index cc60161c33..fc85b554ec 100644 --- a/iMEGA/My Account/Contacts/Cells/ContactTableViewCell.m +++ b/iMEGA/My Account/Contacts/Cells/ContactTableViewCell.m @@ -1,5 +1,7 @@ #import "ContactTableViewCell.h" +@import MEGAL10nObjc; + #ifdef MNZ_SHARE_EXTENSION #import "MEGAShare-Swift.h" #else @@ -93,7 +95,7 @@ - (void)updateAppearance { - (NSString *)userNameForUser:(MEGAUser *)user { NSString *userName; if (user.handle == MEGASdk.currentUserHandle.unsignedLongLongValue) { - userName = [userName stringByAppendingString:[NSString stringWithFormat:@" (%@)", NSLocalizedString(@"me", @"The title for my message in a chat. The message was sent from yourself.")]]; + userName = [userName stringByAppendingString:[NSString stringWithFormat:@" (%@)", LocalizedString(@"me", @"The title for my message in a chat. The message was sent from yourself.")]]; } else { userName = user.mnz_displayName; } @@ -117,7 +119,7 @@ - (void)configureDefaultCellForUser:(MEGAUser *)user newUser:(BOOL)newUser { if (newUser) { self.contactNewView.hidden = NO; - self.contactNewLabel.text = NSLocalizedString(@"New", @"Label shown inside an unseen notification"); + self.contactNewLabel.text = LocalizedString(@"New", @"Label shown inside an unseen notification"); self.contactNewLabel.textColor = UIColor.whiteColor; self.contactNewLabelView.backgroundColor = [UIColor mnz_turquoiseForTraitCollection:self.traitCollection]; } else { @@ -133,7 +135,7 @@ - (void)configureCellForContactsModeFolderSharedWith:(MEGAUser *)user indexPath: if (indexPath.row == 0) { self.permissionsImageView.hidden = YES; self.avatarImageView.image = [UIImage imageNamed:@"inviteToChat"]; - self.nameLabel.text = NSLocalizedString(@"addContactButton", @"Button title to 'Add' the contact to your contacts list"); + self.nameLabel.text = LocalizedString(@"addContactButton", @"Button title to 'Add' the contact to your contacts list"); self.shareLabel.hidden = YES; } else { NSString *userName = [self userNameForUser:user]; @@ -157,15 +159,15 @@ - (void)configureCellForContactsModeChatStartConversation:(ContactsStartConversa self.permissionsImageView.hidden = YES; switch (option) { case ContactsStartConversationNewGroupChat: - self.nameLabel.text = NSLocalizedString(@"New Group Chat", @"Text button for init a group chat"); + self.nameLabel.text = LocalizedString(@"New Group Chat", @"Text button for init a group chat"); self.avatarImageView.image = [UIImage imageNamed:@"createGroup"]; break; case ContactsStartConversationNewMeeting: - self.nameLabel.text = NSLocalizedString(@"meetings.create.newMeeting", @"Text button for init a Meeting."); + self.nameLabel.text = LocalizedString(@"meetings.create.newMeeting", @"Text button for init a Meeting."); self.avatarImageView.image = [UIImage imageNamed:@"newMeeting"]; break; case ContactsStartConversationJoinMeeting: - self.nameLabel.text = NSLocalizedString(@"meetings.link.loggedInUser.joinButtonText", @"Text button for joining a Meeting."); + self.nameLabel.text = LocalizedString(@"meetings.link.loggedInUser.joinButtonText", @"Text button for joining a Meeting."); self.avatarImageView.image = [UIImage imageNamed:@"joinMeeting"]; break; } diff --git a/iMEGA/My Account/Contacts/Cells/ItemCollectionViewCell.h b/iMEGA/My Account/Contacts/Cells/ItemCollectionViewCell.h index fbb5baa6a3..d70eab249e 100644 --- a/iMEGA/My Account/Contacts/Cells/ItemCollectionViewCell.h +++ b/iMEGA/My Account/Contacts/Cells/ItemCollectionViewCell.h @@ -1,4 +1,3 @@ - #import @class MegaAvatarView; diff --git a/iMEGA/My Account/Contacts/Cells/ItemCollectionViewCell.m b/iMEGA/My Account/Contacts/Cells/ItemCollectionViewCell.m index 2a6194e2c8..79d2427fe5 100644 --- a/iMEGA/My Account/Contacts/Cells/ItemCollectionViewCell.m +++ b/iMEGA/My Account/Contacts/Cells/ItemCollectionViewCell.m @@ -1,4 +1,3 @@ - #import "ItemCollectionViewCell.h" #ifdef MNZ_SHARE_EXTENSION diff --git a/iMEGA/My Account/Contacts/ContactDetailsViewController.h b/iMEGA/My Account/Contacts/ContactDetailsViewController.h index 3a94e660b0..f167f425d9 100644 --- a/iMEGA/My Account/Contacts/ContactDetailsViewController.h +++ b/iMEGA/My Account/Contacts/ContactDetailsViewController.h @@ -1,4 +1,3 @@ - #import #import "MEGASdkManager.h" diff --git a/iMEGA/My Account/Contacts/ContactDetailsViewController.m b/iMEGA/My Account/Contacts/ContactDetailsViewController.m index 4708959d3b..83f40a4c9b 100644 --- a/iMEGA/My Account/Contacts/ContactDetailsViewController.m +++ b/iMEGA/My Account/Contacts/ContactDetailsViewController.m @@ -25,6 +25,7 @@ #import "MEGAUser+MNZCategory.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; @import MEGAUIKit; @import MEGASDKRepo; @@ -113,16 +114,16 @@ - (void)viewDidLoad { if (self.contactDetailsMode != ContactDetailsModeMeeting) { [self.backButton setImage:self.backButton.imageView.image.imageFlippedForRightToLeftLayoutDirection forState:UIControlStateNormal]; } else { - NSString *backButtonTitle = NSLocalizedString(@"close", @""); + NSString *backButtonTitle = LocalizedString(@"close", @""); [self.backButton setImage:nil forState:UIControlStateNormal]; [self.backButton setTitle:backButtonTitle forState:UIControlStateNormal]; CGSize size = CGSizeMake(CGFLOAT_MAX, self.backButton.bounds.size.height); self.backButtonWidthConstraint.constant = [backButtonTitle sizeForFont:self.backButton.titleLabel.font size:size mode:NSLineBreakByTruncatingMiddle].width + 20; } [self addMenuToBackButton:self.backButton]; - self.messageLabel.text = NSLocalizedString(@"Message", @"Label for any ‘Message’ button, link, text, title, etc. - (String as short as possible)."); - self.callLabel.text = NSLocalizedString(@"Call", @"Title of the button in the contact info screen to start an audio call"); - self.videoLabel.text = NSLocalizedString(@"Video", @"Title of the button in the contact info screen to start a video call"); + self.messageLabel.text = LocalizedString(@"Message", @"Label for any ‘Message’ button, link, text, title, etc. - (String as short as possible)."); + self.callLabel.text = LocalizedString(@"Call", @"Title of the button in the contact info screen to start an audio call"); + self.videoLabel.text = LocalizedString(@"Video", @"Title of the button in the contact info screen to start a video call"); self.userNickname = self.user.mnz_nickname; @@ -255,7 +256,7 @@ - (ContactTableViewCell *)cellForDNDWithIndexPath:(NSIndexPath *)indexPath { - (ContactTableViewCell *)cellForSharedItemsWithIndexPath:(NSIndexPath *)indexPath { ContactTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactDetailsDefaultTypeID" forIndexPath:indexPath]; cell.avatarImageView.image = [UIImage imageNamed:@"sharedFiles"]; - cell.nameLabel.text = NSLocalizedString(@"Shared Files", @"Header of block with all shared files in chat."); + cell.nameLabel.text = LocalizedString(@"Shared Files", @"Header of block with all shared files in chat."); cell.nameLabel.textColor = UIColor.mnz_label; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; @@ -265,7 +266,7 @@ - (ContactTableViewCell *)cellForSharedItemsWithIndexPath:(NSIndexPath *)indexPa - (ContactTableViewCell *)cellForNicknameWithIndexPath:(NSIndexPath *)indexPath { ContactTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactDetailsDefaultTypeID" forIndexPath:indexPath]; cell.avatarImageView.image = [UIImage imageNamed:@"rename"]; - cell.nameLabel.text = self.userNickname.length == 0 ? NSLocalizedString(@"Set Nickname", @"Contact details screen: Set the alias(nickname) for a user") : NSLocalizedString(@"Edit Nickname", @"Contact details screen: Edit the alias(nickname) for a user"); + cell.nameLabel.text = self.userNickname.length == 0 ? LocalizedString(@"Set Nickname", @"Contact details screen: Set the alias(nickname) for a user") : LocalizedString(@"Edit Nickname", @"Contact details screen: Edit the alias(nickname) for a user"); cell.nameLabel.textColor = UIColor.mnz_label; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; @@ -275,7 +276,7 @@ - (ContactTableViewCell *)cellForNicknameWithIndexPath:(NSIndexPath *)indexPath - (ContactTableViewCell *)cellForVerifyCredentialsWithIndexPath:(NSIndexPath *)indexPath { ContactTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactDetailsVerifyCredentialsTypeID" forIndexPath:indexPath]; cell.avatarImageView.image = [UIImage imageNamed:@"verifyCredentials"]; - cell.nameLabel.text = NSLocalizedString(@"verifyCredentials", @"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."); + cell.nameLabel.text = LocalizedString(@"verifyCredentials", @"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."); cell.nameLabel.textColor = UIColor.mnz_label; cell.permissionsImageView.hidden = !self.areCredentialsVerified; return cell; @@ -286,12 +287,12 @@ - (ContactTableViewCell *)cellForAddAndRemoveContactWithIndexPath:(NSIndexPath * if (self.user.visibility == MEGAUserVisibilityVisible) { //Remove Contact cell.avatarImageView.image = [UIImage imageNamed:@"delete"]; cell.avatarImageView.tintColor = [UIColor mnz_redForTraitCollection:(self.traitCollection)]; - cell.nameLabel.text = NSLocalizedString(@"removeUserTitle", @"Alert title shown when you want to remove one or more contacts"); + cell.nameLabel.text = LocalizedString(@"removeUserTitle", @"Alert title shown when you want to remove one or more contacts"); cell.nameLabel.textColor = [UIColor mnz_redForTraitCollection:(self.traitCollection)]; } else { //Add contact cell.avatarImageView.image = [UIImage imageNamed:@"add"]; cell.avatarImageView.tintColor = [UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]; - cell.nameLabel.text = NSLocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); + cell.nameLabel.text = LocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); } return cell; @@ -323,7 +324,7 @@ - (SharedItemsTableViewCell *)cellForSharedFoldersWithIndexPath:(NSIndexPath *)i - (ContactTableViewCell *)cellForManageChatHistoryWithIndexPath:(NSIndexPath *)indexPath { ContactTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactDetailsDefaultTypeID" forIndexPath:indexPath]; cell.avatarImageView.image = [UIImage imageNamed:@"clearChatHistory"]; - cell.nameLabel.text = NSLocalizedString(@"Manage Chat History", @"Text related with the section where you can manage the chat history. There you can for example, clear the history or configure the retention setting."); + cell.nameLabel.text = LocalizedString(@"Manage Chat History", @"Text related with the section where you can manage the chat history. There you can for example, clear the history or configure the retention setting."); cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; cell.userInteractionEnabled = cell.avatarImageView.userInteractionEnabled = cell.nameLabel.enabled = self.user.visibility == MEGAUserVisibilityVisible && MEGAReachabilityManager.isReachable && [MEGASdkManager.sharedMEGAChatSdk chatConnectionState:self.chatRoom.chatId] == MEGAChatConnectionOnline; @@ -334,7 +335,7 @@ - (ContactTableViewCell *)cellForArchiveChatWithIndexPath:(NSIndexPath *)indexPa ContactTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactDetailsDefaultTypeID" forIndexPath:indexPath]; cell.avatarImageView.image = self.chatRoom.isArchived ? [UIImage imageNamed:@"unArchiveChat"] : [UIImage imageNamed:@"archiveChat"]; cell.avatarImageView.tintColor = self.chatRoom.isArchived ? [UIColor mnz_redForTraitCollection:(self.traitCollection)] : [UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]; - cell.nameLabel.text = self.chatRoom.isArchived ? NSLocalizedString(@"unarchiveChat", @"The title of the dialog to unarchive an archived chat.") : NSLocalizedString(@"archiveChat", @"Title of button to archive chats."); + cell.nameLabel.text = self.chatRoom.isArchived ? LocalizedString(@"unarchiveChat", @"The title of the dialog to unarchive an archived chat.") : LocalizedString(@"archiveChat", @"Title of button to archive chats."); cell.nameLabel.textColor = self.chatRoom.isArchived ? [UIColor mnz_redForTraitCollection:(self.traitCollection)] : UIColor.mnz_label; cell.userInteractionEnabled = cell.avatarImageView.userInteractionEnabled = cell.nameLabel.enabled = MEGAReachabilityManager.isReachable && [MEGASdkManager.sharedMEGAChatSdk chatConnectionState:self.chatRoom.chatId] == MEGAChatConnectionOnline; @@ -345,7 +346,7 @@ - (ContactTableViewCell *)cellForAddParticipantAsContactWithIndexPath:(NSIndexPa ContactTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactDetailsDefaultTypeID" forIndexPath:indexPath]; cell.avatarImageView.image = [UIImage imageNamed:@"add"]; cell.avatarImageView.tintColor = [UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]; - cell.nameLabel.text = NSLocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); + cell.nameLabel.text = LocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email"); cell.userInteractionEnabled = cell.avatarImageView.userInteractionEnabled = cell.nameLabel.enabled = MEGAReachabilityManager.isReachable; return cell; @@ -354,7 +355,7 @@ - (ContactTableViewCell *)cellForAddParticipantAsContactWithIndexPath:(NSIndexPa - (ContactTableViewCell *)cellForSetPermissionWithIndexPath:(NSIndexPath *)indexPath { ContactTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactDetailsPermissionsTypeID" forIndexPath:indexPath]; cell.avatarImageView.image = [UIImage imageNamed:@"readWritePermissions"]; - cell.nameLabel.text = NSLocalizedString(@"permissions", @"Title of the view that shows the kind of permissions (Read Only, Read & Write or Full Access) that you can give to a shared folder"); + cell.nameLabel.text = LocalizedString(@"permissions", @"Title of the view that shows the kind of permissions (Read Only, Read & Write or Full Access) that you can give to a shared folder"); MEGAChatRoomPrivilege privilege = [self.groupChatRoom peerPrivilegeByHandle:self.userHandle]; switch (privilege) { case MEGAChatRoomPrivilegeUnknown: @@ -362,15 +363,15 @@ - (ContactTableViewCell *)cellForSetPermissionWithIndexPath:(NSIndexPath *)index break; case MEGAChatRoomPrivilegeRo: - cell.permissionsLabel.text = NSLocalizedString(@"readOnly", @"Permissions given to the user you share your folder with"); + cell.permissionsLabel.text = LocalizedString(@"readOnly", @"Permissions given to the user you share your folder with"); break; case MEGAChatRoomPrivilegeStandard: - cell.permissionsLabel.text = NSLocalizedString(@"standard", @"The Standard permission level in chat. With the standard permissions a participant can read and type messages in a chat."); + cell.permissionsLabel.text = LocalizedString(@"standard", @"The Standard permission level in chat. With the standard permissions a participant can read and type messages in a chat."); break; case MEGAChatRoomPrivilegeModerator: - cell.permissionsLabel.text = NSLocalizedString(@"moderator", @"The Moderator permission level in chat. With moderator permissions a participant can manage the chat."); + cell.permissionsLabel.text = LocalizedString(@"moderator", @"The Moderator permission level in chat. With moderator permissions a participant can manage the chat."); break; } @@ -382,7 +383,7 @@ - (ContactTableViewCell *)cellForSetPermissionWithIndexPath:(NSIndexPath *)index - (ContactTableViewCell *)cellForRemoveParticipantWithIndexPath:(NSIndexPath *)indexPath { ContactTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactDetailsDefaultTypeID" forIndexPath:indexPath]; cell.avatarImageView.image = [UIImage imageNamed:@"delete"]; - cell.nameLabel.text = NSLocalizedString(@"removeParticipant", @"A button title which removes a participant from a chat."); + cell.nameLabel.text = LocalizedString(@"removeParticipant", @"A button title which removes a participant from a chat."); cell.nameLabel.textColor = [UIColor mnz_redForTraitCollection:(self.traitCollection)]; cell.userInteractionEnabled = cell.avatarImageView.userInteractionEnabled = cell.nameLabel.enabled = MEGAReachabilityManager.isReachable && [MEGASdkManager.sharedMEGAChatSdk chatConnectionState:self.groupChatRoom.chatId] == MEGAChatConnectionOnline; @@ -404,11 +405,11 @@ - (void)updateAppearance { } - (void)showArchiveChatAlertAtIndexPath { - NSString *title = self.chatRoom.isArchived ? NSLocalizedString(@"unarchiveChatMessage", @"Confirmation message for user to confirm it will unarchive an archived chat.") : NSLocalizedString(@"archiveChatMessage", @"Confirmation message on archive chat dialog for user to confirm."); + NSString *title = self.chatRoom.isArchived ? LocalizedString(@"unarchiveChatMessage", @"Confirmation message for user to confirm it will unarchive an archived chat.") : LocalizedString(@"archiveChatMessage", @"Confirmation message on archive chat dialog for user to confirm."); UIAlertController *leaveAlertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; - [leaveAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [leaveAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - [leaveAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [leaveAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { MEGAArchiveChatRequestDelegate *archiveChatRequesDelegate = [[MEGAArchiveChatRequestDelegate alloc] initWithCompletion:^(MEGAChatRoom *chatRoom) { if (chatRoom.isArchived) { if (self.navigationController.childViewControllers.count >= 3) { @@ -430,7 +431,7 @@ - (void)showArchiveChatAlertAtIndexPath { - (void)showPermissionAlertWithSourceView:(UIView *)sourceView { MEGAChatGenericRequestDelegate *delegate = [MEGAChatGenericRequestDelegate.alloc initWithCompletion:^(MEGAChatRequest * _Nonnull request, MEGAChatError * _Nonnull error) { if (error.type) { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(error.name, nil)]; + [SVProgressHUD showErrorWithStatus:LocalizedString(error.name, @"")]; } else { self.groupChatRoom = [MEGASdkManager.sharedMEGAChatSdk chatRoomForChatId:request.chatHandle]; [self.tableView reloadData]; @@ -442,24 +443,24 @@ - (void)showPermissionAlertWithSourceView:(UIView *)sourceView { UIImageView *checkmarkImageView = [UIImageView.alloc initWithImage:[UIImage imageNamed:@"turquoise_checkmark"]]; NSMutableArray *actions = NSMutableArray.new; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"moderator", @"The Moderator permission level in chat. With moderator permissions a participant can manage the chat.") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeModerator ? checkmarkImageView : nil image:[UIImage imageNamed:@"moderator"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"moderator", @"The Moderator permission level in chat. With moderator permissions a participant can manage the chat.") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeModerator ? checkmarkImageView : nil image:[UIImage imageNamed:@"moderator"] style:UIAlertActionStyleDefault actionHandler:^{ [MEGASdkManager.sharedMEGAChatSdk updateChatPermissions:weakSelf.groupChatRoom.chatId userHandle:weakSelf.userHandle privilege:MEGAChatRoomPrivilegeModerator delegate:delegate]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"standard", @"The Standard permission level in chat. With the standard permissions a participant can read and type messages in a chat.") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeStandard ? checkmarkImageView : nil image:[UIImage imageNamed:@"standard"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"standard", @"The Standard permission level in chat. With the standard permissions a participant can read and type messages in a chat.") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeStandard ? checkmarkImageView : nil image:[UIImage imageNamed:@"standard"] style:UIAlertActionStyleDefault actionHandler:^{ [MEGASdkManager.sharedMEGAChatSdk updateChatPermissions:weakSelf.groupChatRoom.chatId userHandle:weakSelf.userHandle privilege:MEGAChatRoomPrivilegeStandard delegate:delegate]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"readOnly", @"Permissions given to the user you share your folder with") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeRo ? checkmarkImageView : nil image:[UIImage imageNamed:@"readOnly_chat"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"readOnly", @"Permissions given to the user you share your folder with") detail:nil accessoryView:privilege == MEGAChatRoomPrivilegeRo ? checkmarkImageView : nil image:[UIImage imageNamed:@"readOnly_chat"] style:UIAlertActionStyleDefault actionHandler:^{ [MEGASdkManager.sharedMEGAChatSdk updateChatPermissions:weakSelf.groupChatRoom.chatId userHandle:weakSelf.userHandle privilege:MEGAChatRoomPrivilegeRo delegate:delegate]; }]]; - ActionSheetViewController *permissionsActionSheet = [ActionSheetViewController.alloc initWithActions:actions headerTitle:NSLocalizedString(@"permissions", @"Title of the view that shows the kind of permissions (Read Only, Read & Write or Full Access) that you can give to a shared folder ") dismissCompletion:nil sender:sourceView]; + ActionSheetViewController *permissionsActionSheet = [ActionSheetViewController.alloc initWithActions:actions headerTitle:LocalizedString(@"permissions", @"Title of the view that shows the kind of permissions (Read Only, Read & Write or Full Access) that you can give to a shared folder ") dismissCompletion:nil sender:sourceView]; [self presentViewController:permissionsActionSheet animated:YES completion:nil]; } - (void)removeParticipantFromGroup { MEGAChatGenericRequestDelegate *delegate = [MEGAChatGenericRequestDelegate.alloc initWithCompletion:^(MEGAChatRequest * _Nonnull request, MEGAChatError * _Nonnull error) { if (error.type) { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(error.name, nil)]; + [SVProgressHUD showErrorWithStatus:LocalizedString(error.name, @"")]; } else { [self.navigationController popViewControllerAnimated:YES]; } @@ -967,7 +968,7 @@ - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger if (section == 0) { [headerView configureWithTitle:nil topDistance:24.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; } else if ([self isSharedFolderSection:section]) { - [headerView configureWithTitle:NSLocalizedString(@"sharedFolders", @"Title of the incoming shared folders of a user.") topDistance:4.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; + [headerView configureWithTitle:LocalizedString(@"sharedFolders", @"Title of the incoming shared folders of a user.") topDistance:4.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; } else { [headerView configureWithTitle:nil topDistance:0.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; } @@ -1081,16 +1082,7 @@ - (void)nodeAction:(NodeActionViewController *)nodeAction didSelect:(MegaNodeAct break; case MegaNodeActionTypeFavourite: { - MEGAGenericRequestDelegate *delegate = [MEGAGenericRequestDelegate.alloc initWithCompletion:^(MEGARequest * _Nonnull request, MEGAError * _Nonnull error) { - if (error.type == MEGAErrorTypeApiOk) { - if (request.numDetails == 1) { - [[QuickAccessWidgetManager.alloc init] insertFavouriteItemFor:node]; - } else { - [[QuickAccessWidgetManager.alloc init] deleteFavouriteItemFor:node]; - } - } - }]; - [MEGASdkManager.sharedMEGASdk setNodeFavourite:node favourite:!node.isFavourite delegate:delegate]; + [MEGASdk.shared setNodeFavourite:node favourite:!node.isFavourite]; break; } diff --git a/iMEGA/My Account/Contacts/ContactDetailsViewController.swift b/iMEGA/My Account/Contacts/ContactDetailsViewController.swift index 86338ab194..4deb3b991d 100644 --- a/iMEGA/My Account/Contacts/ContactDetailsViewController.swift +++ b/iMEGA/My Account/Contacts/ContactDetailsViewController.swift @@ -1,9 +1,10 @@ import MEGADomain +import MEGAL10n extension ContactDetailsViewController { @objc func joinMeeting(withChatRoom chatRoom: MEGAChatRoom) { - guard let call = MEGASdkManager.sharedMEGAChatSdk().chatCall(forChatId: chatRoom.chatId) else { return } - let isSpeakerEnabled = AVAudioSession.sharedInstance().mnz_isOutputEqual(toPortType: .builtInSpeaker) + guard let call = MEGAChatSdk.shared.chatCall(forChatId: chatRoom.chatId) else { return } + let isSpeakerEnabled = AVAudioSession.sharedInstance().isOutputEqualToPortType(.builtInSpeaker) MeetingContainerRouter(presenter: self, chatRoom: chatRoom.toChatRoomEntity(), call: call.toCallEntity(), diff --git a/iMEGA/My Account/Contacts/ContactLinkQR/ContactLinkQRViewController.h b/iMEGA/My Account/Contacts/ContactLinkQR/ContactLinkQRViewController.h index 545f1124ff..e7d3eb3ad7 100644 --- a/iMEGA/My Account/Contacts/ContactLinkQR/ContactLinkQRViewController.h +++ b/iMEGA/My Account/Contacts/ContactLinkQR/ContactLinkQRViewController.h @@ -1,4 +1,3 @@ - #import #import "ContacLinkQRType.h" diff --git a/iMEGA/My Account/Contacts/ContactLinkQR/ContactLinkQRViewController.m b/iMEGA/My Account/Contacts/ContactLinkQR/ContactLinkQRViewController.m index e69316db54..61867341ea 100644 --- a/iMEGA/My Account/Contacts/ContactLinkQR/ContactLinkQRViewController.m +++ b/iMEGA/My Account/Contacts/ContactLinkQR/ContactLinkQRViewController.m @@ -1,4 +1,3 @@ - #import "ContactLinkQRViewController.h" #import @@ -17,6 +16,8 @@ #import "UIImage+GKContact.h" #import "UIImage+MNZCategory.h" #import "UIImageView+MNZCategory.h" + +@import MEGAL10nObjc; @import MEGASDKRepo; typedef NS_ENUM(NSInteger, QRSection) { @@ -58,11 +59,11 @@ @implementation ContactLinkQRViewController - (void)viewDidLoad { [super viewDidLoad]; - [self.segmentedControl setTitle:NSLocalizedString(@"My QR code", @"Label for any ‘My QR code’ button, link, text, title, etc. - (String as short as possible).") forSegmentAtIndex:0]; - [self.segmentedControl setTitle:NSLocalizedString(@"scanCode", @"Segmented control title for view that allows the user to scan QR codes. String as short as possible.") forSegmentAtIndex:1]; - [self.linkCopyButton setTitle:NSLocalizedString(@"copyLink", @"Title for a button to copy the link to the clipboard") forState:UIControlStateNormal]; + [self.segmentedControl setTitle:LocalizedString(@"My QR code", @"Label for any ‘My QR code’ button, link, text, title, etc. - (String as short as possible).") forSegmentAtIndex:0]; + [self.segmentedControl setTitle:LocalizedString(@"scanCode", @"Segmented control title for view that allows the user to scan QR codes. String as short as possible.") forSegmentAtIndex:1]; + [self.linkCopyButton setTitle:LocalizedString(@"copyLink", @"Title for a button to copy the link to the clipboard") forState:UIControlStateNormal]; - self.hintLabel.text = NSLocalizedString(@"lineCodeWithCamera", @"Label that encourage the user to line the QR to scan with the camera"); + self.hintLabel.text = LocalizedString(@"lineCodeWithCamera", @"Label that encourage the user to line the QR to scan with the camera"); if (self.scanCode) { self.segmentedControl.selectedSegmentIndex = QRSectionScanCode; @@ -227,7 +228,7 @@ - (IBAction)backButtonTapped:(UIButton *)sender { - (IBAction)linkCopyButtonTapped:(UIButton *)sender { [UIPasteboard generalPasteboard].string = self.contactLinkLabel.text; - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"copiedToTheClipboard", @"Text of the button after the links were copied to the clipboard")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"copiedToTheClipboard", @"Text of the button after the links were copied to the clipboard")]; } #pragma mark - QR recognizing @@ -361,13 +362,13 @@ - (void)presentInviteModalForEmail:(NSString *)email fullName:(NSString *)fullNa MEGAUser *user = [[MEGASdkManager sharedMEGASdk] contactForEmail:email]; if (user && user.visibility == MEGAUserVisibilityVisible) { - inviteOrDismissModal.detail = [NSLocalizedString(@"alreadyAContact", @"Error message displayed when trying to invite a contact who is already added.") stringByReplacingOccurrencesOfString:@"%s" withString:email]; - inviteOrDismissModal.firstButtonTitle = NSLocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); + inviteOrDismissModal.detail = [LocalizedString(@"alreadyAContact", @"Error message displayed when trying to invite a contact who is already added.") stringByReplacingOccurrencesOfString:@"%s" withString:email]; + inviteOrDismissModal.firstButtonTitle = LocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); inviteOrDismissModal.firstCompletion = dismissCompletion; } else { BOOL isInOutgoingContactRequest = NO; MEGAContactRequestList *outgoingContactRequestList = [[MEGASdkManager sharedMEGASdk] outgoingContactRequests]; - for (NSInteger i = 0; i < outgoingContactRequestList.size.integerValue; i++) { + for (NSInteger i = 0; i < outgoingContactRequestList.size; i++) { MEGAContactRequest *contactRequest = [outgoingContactRequestList contactRequestAtIndex:i]; if ([email isEqualToString:contactRequest.targetEmail]) { isInOutgoingContactRequest = YES; @@ -376,17 +377,17 @@ - (void)presentInviteModalForEmail:(NSString *)email fullName:(NSString *)fullNa } if (isInOutgoingContactRequest) { inviteOrDismissModal.image = [UIImage imageNamed:@"inviteSent"]; - inviteOrDismissModal.viewTitle = NSLocalizedString(@"inviteSent", @"Title shown when the user sends a contact invitation"); - NSString *detailText = NSLocalizedString(@"dialog.inviteContact.outgoingContactRequest", @"Detail message shown when a contact has been invited. The [X] placeholder will be replaced on runtime for the email of the invited user"); + inviteOrDismissModal.viewTitle = LocalizedString(@"inviteSent", @"Title shown when the user sends a contact invitation"); + NSString *detailText = LocalizedString(@"dialog.inviteContact.outgoingContactRequest", @"Detail message shown when a contact has been invited. The [X] placeholder will be replaced on runtime for the email of the invited user"); detailText = [detailText stringByReplacingOccurrencesOfString:@"[X]" withString:email]; inviteOrDismissModal.detail = detailText; inviteOrDismissModal.boldInDetail = email; - inviteOrDismissModal.firstButtonTitle = NSLocalizedString(@"close", nil); + inviteOrDismissModal.firstButtonTitle = LocalizedString(@"close", @""); inviteOrDismissModal.firstCompletion = dismissCompletion; } else { inviteOrDismissModal.detail = email; - inviteOrDismissModal.firstButtonTitle = NSLocalizedString(@"invite", @"A button on a dialog which invites a contact to join MEGA."); - inviteOrDismissModal.dismissButtonTitle = NSLocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); + inviteOrDismissModal.firstButtonTitle = LocalizedString(@"invite", @"A button on a dialog which invites a contact to join MEGA."); + inviteOrDismissModal.dismissButtonTitle = LocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); inviteOrDismissModal.firstCompletion = firstCompletion; inviteOrDismissModal.dismissCompletion = dismissCompletion; } @@ -396,7 +397,7 @@ - (void)presentInviteModalForEmail:(NSString *)email fullName:(NSString *)fullNa } - (void)feedbackWithSuccess:(BOOL)success { - NSString *message = success ? NSLocalizedString(@"codeScanned", @"Success text shown in a label when the user scans a valid QR. String as short as possible.") : NSLocalizedString(@"invalidCode", @"Error text shown when the user scans a QR that is not valid. String as short as possible."); + NSString *message = success ? LocalizedString(@"codeScanned", @"Success text shown in a label when the user scans a valid QR. String as short as possible.") : LocalizedString(@"invalidCode", @"Error text shown when the user scans a QR that is not valid. String as short as possible."); UIColor *color = success ? [UIColor greenColor] : [UIColor redColor]; dispatch_async(dispatch_get_main_queue(), ^{ self.errorLabel.text = message; diff --git a/iMEGA/My Account/Contacts/ContactRequestsViewController.m b/iMEGA/My Account/Contacts/ContactRequestsViewController.m index d96782222e..55804b6aea 100644 --- a/iMEGA/My Account/Contacts/ContactRequestsViewController.m +++ b/iMEGA/My Account/Contacts/ContactRequestsViewController.m @@ -1,4 +1,3 @@ - #import "ContactRequestsViewController.h" #import "UIScrollView+EmptyDataSet.h" @@ -13,6 +12,8 @@ #import "NSString+MNZCategory.h" #import "NSDate+MNZCategory.h" +@import MEGAL10nObjc; + typedef NS_ENUM(NSInteger, Segment) { SegmentReceived = 0, SegmentSent @@ -56,10 +57,10 @@ - (void)viewWillAppear:(BOOL)animated { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(internetConnectionChanged) name:kReachabilityChangedNotification object:nil]; - [self.navigationItem setTitle:NSLocalizedString(@"contactRequests", @"Contact requests")]; + [self.navigationItem setTitle:LocalizedString(@"contactRequests", @"Contact requests")]; - [self.contactRequestsSegmentedControl setTitle:NSLocalizedString(@"received", @"Title of one of the filters in 'Contacts requests' section. If 'Received' is selected, it will only show the requests which have been recieved.") forSegmentAtIndex:SegmentReceived]; - [self.contactRequestsSegmentedControl setTitle:NSLocalizedString(@"sent", @"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.") forSegmentAtIndex:SegmentSent]; + [self.contactRequestsSegmentedControl setTitle:LocalizedString(@"received", @"Title of one of the filters in 'Contacts requests' section. If 'Received' is selected, it will only show the requests which have been recieved.") forSegmentAtIndex:SegmentReceived]; + [self.contactRequestsSegmentedControl setTitle:LocalizedString(@"sent", @"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.") forSegmentAtIndex:SegmentSent]; [[MEGASdkManager sharedMEGASdk] addMEGAGlobalDelegate:self]; [[MEGAReachabilityManager sharedManager] retryPendingConnections]; @@ -128,16 +129,16 @@ - (void)reloadOutgoingContactsRequestsView { self.outgoingContactRequestArray = [[NSMutableArray alloc] init]; MEGAContactRequestList *outgoingContactRequestList = [[MEGASdkManager sharedMEGASdk] outgoingContactRequests]; - for (NSInteger i = 0; i < [[outgoingContactRequestList size] integerValue]; i++) { + for (NSInteger i = 0; i < outgoingContactRequestList.size; i++) { MEGAContactRequest *contactRequest = [outgoingContactRequestList contactRequestAtIndex:i]; [self.outgoingContactRequestArray addObject:contactRequest]; } //If user cancel all sent requests and HAS incoming requests > Switch to Received //If user cancel all sent requests and HAS NOT incoming requests > Go back to Contacts - if (outgoingContactRequestList.size.unsignedIntValue == 0 && self.isDeletingLastRequest) { + if (outgoingContactRequestList.size == 0 && self.isDeletingLastRequest) { MEGAContactRequestList *incomingContactRequestList = MEGASdkManager.sharedMEGASdk.incomingContactRequests; - if (incomingContactRequestList.size.unsignedIntValue == 0) { + if (incomingContactRequestList.size == 0) { if (self.presentingViewController) { [self dismissViewControllerAnimated:YES completion:nil]; } else { @@ -155,14 +156,14 @@ - (void)reloadIncomingContactsRequestsView { self.incomingContactRequestArray = [[NSMutableArray alloc] init]; MEGAContactRequestList *incomingContactRequestList = [[MEGASdkManager sharedMEGASdk] incomingContactRequests]; - for (NSInteger i = 0; i < [[incomingContactRequestList size] integerValue]; i++) { + for (NSInteger i = 0; i < incomingContactRequestList.size; i++) { MEGAContactRequest *contactRequest = [incomingContactRequestList contactRequestAtIndex:i]; [self.incomingContactRequestArray addObject:contactRequest]; } //If user accepts all received requests > Go back to Contacts //If user accepts all received requests and HAS sent requests > Go back to Contacts - if (incomingContactRequestList.size.unsignedIntValue == 0 && self.isAcceptingOrDecliningLastRequest) { + if (incomingContactRequestList.size == 0 && self.isAcceptingOrDecliningLastRequest) { if (self.presentingViewController) { [self dismissViewControllerAnimated:YES completion:nil]; } else { @@ -301,9 +302,9 @@ - (nullable UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView { - (NSString *)titleForEmptyState { NSString *text; if ([MEGAReachabilityManager isReachable]) { - text = NSLocalizedString(@"noRequestPending", nil); + text = LocalizedString(@"noRequestPending", @""); } else { - text = NSLocalizedString(@"noInternetConnection", @"No Internet Connection"); + text = LocalizedString(@"noInternetConnection", @"No Internet Connection"); } return text; @@ -312,7 +313,7 @@ - (NSString *)titleForEmptyState { - (NSString *)descriptionForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); + text = LocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); } return text; @@ -329,7 +330,7 @@ - (UIImage *)imageForEmptyState { - (NSString *)buttonTitleForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); + text = LocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); } return text; @@ -353,7 +354,7 @@ - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if ([error type]) { if ([request type] == MEGARequestTypeInviteContact) { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(error.name, nil)]; + [SVProgressHUD showErrorWithStatus:LocalizedString(error.name, @"")]; } self.performingRequest = NO; return; @@ -380,7 +381,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGARequestTypeInviteContact: switch (request.number.integerValue) { case 1: - [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"requestCancelled", nil)]; + [SVProgressHUD showErrorWithStatus:LocalizedString(@"requestCancelled", @"")]; break; default: @@ -392,11 +393,11 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGARequestTypeReplyContactRequest: switch (request.number.integerValue) { case 0: - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"requestAccepted", nil)]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"requestAccepted", @"")]; break; case 1: - [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"requestDeleted", nil)]; + [SVProgressHUD showErrorWithStatus:LocalizedString(@"requestDeleted", @"")]; break; default: diff --git a/iMEGA/My Account/Contacts/ContactsGroups/ContactsGroupTableViewCell.swift b/iMEGA/My Account/Contacts/ContactsGroups/ContactsGroupTableViewCell.swift index 61511aa185..038ffd12ae 100644 --- a/iMEGA/My Account/Contacts/ContactsGroups/ContactsGroupTableViewCell.swift +++ b/iMEGA/My Account/Contacts/ContactsGroups/ContactsGroupTableViewCell.swift @@ -1,4 +1,3 @@ - import MEGASDKRepo import UIKit @@ -13,7 +12,7 @@ class ContactsGroupTableViewCell: UITableViewCell { titleLabel.text = chatListItem.chatTitle() keyRotationImage.isHidden = chatListItem.isPublicChat - guard let chatRoom = MEGASdkManager.sharedMEGAChatSdk().chatRoom(forChatId: chatListItem.chatId) else { + guard let chatRoom = MEGAChatSdk.shared.chatRoom(forChatId: chatListItem.chatId) else { return } backAvatarImage.mnz_setImage(forUserHandle: chatRoom.peerHandle(at: 0)) diff --git a/iMEGA/My Account/Contacts/ContactsGroups/ContactsGroupsViewController.swift b/iMEGA/My Account/Contacts/ContactsGroups/ContactsGroupsViewController.swift index 4ee4077433..589b70a320 100644 --- a/iMEGA/My Account/Contacts/ContactsGroups/ContactsGroupsViewController.swift +++ b/iMEGA/My Account/Contacts/ContactsGroups/ContactsGroupsViewController.swift @@ -1,4 +1,5 @@ - +import ChatRepo +import MEGAL10n import MEGAUIKit import UIKit @@ -50,7 +51,7 @@ class ContactsGroupsViewController: UIViewController { } func fetchGroupChatsList() { - guard let chatListItems = MEGASdkManager.sharedMEGAChatSdk().chatListItems else { + guard let chatListItems = MEGAChatSdk.shared.chatListItems else { return } @@ -66,7 +67,7 @@ class ContactsGroupsViewController: UIViewController { } func addNewChatToList(chatRoom: MEGAChatRoom) { - guard let newChatListItem = MEGASdkManager.sharedMEGAChatSdk().chatListItem(forChatId: chatRoom.chatId) else { + guard let newChatListItem = MEGAChatSdk.shared.chatListItem(forChatId: chatRoom.chatId) else { return } groupChats.append(newChatListItem) @@ -79,7 +80,7 @@ class ContactsGroupsViewController: UIViewController { func showGroupChatRoom(at indexPath: IndexPath) { let chatListItem = searchingGroupChats.isNotEmpty ? searchingGroupChats[indexPath.row] : groupChats[indexPath.row] - guard let chatRoom = MEGASdkManager.sharedMEGAChatSdk().chatRoom(forChatId: chatListItem.chatId) else { + guard let chatRoom = MEGAChatSdk.shared.chatRoom(forChatId: chatListItem.chatId) else { return } let messagesVC = ChatViewController(chatRoom: chatRoom) @@ -98,7 +99,7 @@ class ContactsGroupsViewController: UIViewController { } if keyRotation { - MEGASdkManager.sharedMEGAChatSdk().mnz_createChatRoom( + MEGAChatSdk.shared.mnz_createChatRoom( usersArray: users, title: groupName, allowNonHostToAddParticipants: allowNonHostToAddParticipants, @@ -110,35 +111,36 @@ class ContactsGroupsViewController: UIViewController { } }) } else { - MEGASdkManager.sharedMEGAChatSdk().createPublicChat( + MEGAChatSdk.shared.createPublicChat( withPeers: MEGAChatPeerList.mnz_standardPrivilegePeerList(usersArray: users), title: groupName, speakRequest: false, waitingRoom: false, openInvite: allowNonHostToAddParticipants, - delegate: MEGAChatGenericRequestDelegate.init(completion: { request, error in - guard let chatRoom = MEGASdkManager.sharedMEGAChatSdk().chatRoom(forChatId: request.chatHandle) else { + delegate: ChatRequestDelegate { result in + guard case let .success(request) = result, + let chatRoom = MEGAChatSdk.shared.chatRoom(forChatId: request.chatHandle) else { return } DispatchQueue.main.async { self?.addNewChatToList(chatRoom: chatRoom) } if getChatlink { - MEGASdkManager.sharedMEGAChatSdk().createChatLink(chatRoom.chatId, delegate: MEGAChatGenericRequestDelegate.init(completion: { request, error in - if error.type == .MEGAChatErrorTypeOk { + MEGAChatSdk.shared.createChatLink(chatRoom.chatId, delegate: ChatRequestDelegate { result in + if case .success = result { let messagesVC = ChatViewController(chatRoom: chatRoom) messagesVC.publicChatWithLinkCreated = true messagesVC.publicChatLink = URL(string: request.text) self?.navigationController?.pushViewController(messagesVC, animated: true) } - })) + }) } else { DispatchQueue.main.async { let messagesVC = ChatViewController(chatRoom: chatRoom) self?.navigationController?.pushViewController(messagesVC, animated: true) } } - })) + }) } } diff --git a/iMEGA/My Account/Contacts/ContactsPicker/ContactsPicker.storyboard b/iMEGA/My Account/Contacts/ContactsPicker/ContactsPicker.storyboard deleted file mode 100644 index 10bca0907f..0000000000 --- a/iMEGA/My Account/Contacts/ContactsPicker/ContactsPicker.storyboard +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iMEGA/My Account/Contacts/ContactsPicker/ContactsPickerViewController.swift b/iMEGA/My Account/Contacts/ContactsPicker/ContactsPickerViewController.swift deleted file mode 100644 index d0f9991061..0000000000 --- a/iMEGA/My Account/Contacts/ContactsPicker/ContactsPickerViewController.swift +++ /dev/null @@ -1,345 +0,0 @@ -import MEGAPermissions -import MEGAUIKit -import UIKit - -@objc protocol ContactsPickerViewControllerDelegate { - func contactsPicker(_ contactsPicker: ContactsPickerViewController, didSelectContacts values: [String]) -} - -class ContactsPickerViewController: UIViewController { - - @IBOutlet weak var tableView: UITableView! - - private lazy var searchController = UISearchController() - - private var keys = [String]() - - private var contacts = [DeviceContact]() - private var deviceContactsOperation: DeviceContactsOperation? - - private var contactsSections = [String: [DeviceContact]]() - private var contactsSectionTitles = [String]() - private lazy var searchingContacts = [DeviceContact]() - private lazy var selectedContacts = Set() - - private var delegate: (any ContactsPickerViewControllerDelegate)? - - private lazy var selectAllBarButton: UIBarButtonItem = UIBarButtonItem(image: Asset.Images.NavigationBar.selectAll.image, style: .plain, target: self, action: #selector(selectAllTapped) - ) - - private lazy var sendBarButton: UIBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: self, action: #selector(sendTapped) - ) - - @objc class func instantiate(withContactKeys keys: [String], delegate: any ContactsPickerViewControllerDelegate) -> ContactsPickerViewController { - guard let contactsPickerVC = UIStoryboard(name: "ContactsPicker", bundle: nil).instantiateViewController(withIdentifier: "ContactsPickerViewControllerID") as? ContactsPickerViewController else { - fatalError("Could not instantiate ContactsPickerViewController") - } - - contactsPickerVC.keys = keys - contactsPickerVC.delegate = delegate - return contactsPickerVC - } - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - navigationController?.presentationController?.delegate = self - - configureView() - fetchDeviceContacts() - } - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - - if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { - AppearanceManager.forceSearchBarUpdate(searchController.searchBar, traitCollection: traitCollection) - - updateAppearance() - } - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - if let operation = deviceContactsOperation, operation.isExecuting { - SVProgressHUD.dismiss() - DeviceContactsManager.shared.cancelDeviceContactsOperation(operation) - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - } - - // MARK: - Private - - private func addSearchController() { - searchController = UISearchController.customSearchController(searchResultsUpdaterDelegate: self, searchBarDelegate: self) - searchController.hidesNavigationBarDuringPresentation = false - navigationItem.searchController = searchController - navigationItem.hidesSearchBarWhenScrolling = false - } - - private func configureView() { - title = Strings.Localizable.contactsTitle - guard let megaNavigation = navigationController as? MEGANavigationController else { - fatalError("Could not access MEGANavigationController") - } - megaNavigation.addLeftDismissButton(withText: Strings.Localizable.cancel) - - navigationItem.rightBarButtonItem = selectAllBarButton - selectAllBarButton.isEnabled = false - - addSearchController() - - tableView.tableFooterView = UIView() // This remove the separator line between empty cells - updateAppearance() - - let flexibleSpaceBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) - toolbarItems = [flexibleSpaceBarButton, sendBarButton] - } - - private func fetchDeviceContacts() { - SVProgressHUD.show(withStatus: Strings.Localizable.loading) - - let deviceContactsOperation = DeviceContactsOperation(keys) - deviceContactsOperation.completionBlock = { [weak self] in - if deviceContactsOperation.isCancelled { - return - } - - DispatchQueue.main.async { - self?.prepareDataSource(forContacts: deviceContactsOperation.fetchedContacts) - SVProgressHUD.dismiss() - self?.selectAllBarButton.isEnabled = true - } - } - - DeviceContactsManager.shared.addGetDeviceContactsOperation(deviceContactsOperation) - - self.deviceContactsOperation = deviceContactsOperation - } - - private func prepareDataSource(forContacts fetchedContacts: [DeviceContact]) { - contacts = fetchedContacts - contactsSections = Dictionary(grouping: contacts, by: {String($0.name.uppercased().prefix(1))}) - contactsSectionTitles = contactsSections.keys.sorted() - tableView.emptyDataSetSource = self - tableView.reloadData() - } - - private func updateAppearance() { - view.backgroundColor = UIColor.mnz_backgroundGrouped(for: traitCollection) - tableView.separatorColor = UIColor.mnz_separator(for: traitCollection) - tableView.reloadData() - } - - @objc private func sendTapped() { - searchController.isActive = false - - dismiss(animated: true) { - self.delegate?.contactsPicker(self, didSelectContacts: self.selectedContacts.map({ $0.contactDetail })) - } - } - - @objc private func selectAllTapped() { - if contacts.count == tableView.indexPathsForSelectedRows?.count { - selectedContacts.removeAll() - for section in 0.. Int { - return isSearching ? 1 : contactsSectionTitles.count - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return isSearching ? searchingContacts.count : contactsSections[contactsSectionTitles[section]]?.count ?? 0 - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let contact = isSearching ? searchingContacts[indexPath.row] : contactsSections[contactsSectionTitles[indexPath.section]]?[indexPath.row] else { - fatalError("Could not get device contact at index path") - } - guard let cell = tableView.dequeueReusableCell(withIdentifier: DeviceContactTableViewCell.reuseIdentifier, for: indexPath) as? DeviceContactTableViewCell else { - fatalError("Could not dequeue cell with identifier deviceContactCell") - } - cell.configure(for: contact) - - if selectedContacts.contains(contact) { - tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none) - } else { - tableView.deselectRow(at: indexPath, animated: true) - } - - return cell - } - - func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return isSearching ? nil : contactsSectionTitles[section] - } - - func sectionIndexTitles(for tableView: UITableView) -> [String]? { - return isSearching ? nil : contactsSectionTitles - } - - func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { - return contactsSectionTitles.firstIndex(of: title) ?? 0 - } -} - -// MARK: - UITableViewDelegate - -extension ContactsPickerViewController: UITableViewDelegate { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let contact = isSearching ? searchingContacts[indexPath.row] : contactsSections[contactsSectionTitles[indexPath.section]]?[indexPath.row] else { - fatalError("Could not get device contact at index path") - } - - selectedContacts.insert(contact) - if selectedContacts.count == 1 { - navigationController?.setToolbarHidden(false, animated: true) - } - updateToolbar() - } - - func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { - guard let contact = isSearching ? searchingContacts[indexPath.row] : contactsSections[contactsSectionTitles[indexPath.section]]?[indexPath.row] else { - fatalError("Could not get device contact at index path") - } - - selectedContacts.remove(contact) - if selectedContacts.isEmpty { - navigationController?.setToolbarHidden(true, animated: true) - } - updateToolbar() - } - - func updateToolbar() { - sendBarButton.title = String(format: "%@ (%d)", Strings.Localizable.send, selectedContacts.count) - } -} - -// MARK: - DZNEmptyDataSetSource - -extension ContactsPickerViewController: DZNEmptyDataSetSource { - func customView(forEmptyDataSet scrollView: UIScrollView) -> UIView? { - let emptyStateView = EmptyStateView.init(image: imageForEmptyDataSet(), title: titleForEmptyDataSet(), description: nil, buttonTitle: buttonTitleForEmptyDataSet()) - emptyStateView.button?.addTarget(self, action: #selector(emptyStateButtonTouchUpInside), for: .touchUpInside) - return emptyStateView - } - - func imageForEmptyDataSet() -> UIImage? { - if self.searchController.isActive && self.searchController.searchBar.text?.isNotEmpty == true { - return Asset.Images.EmptyStates.searchEmptyState.image - } else { - return Asset.Images.EmptyStates.contactsEmptyState.image - } - } - - private var permissionHandler: any DevicePermissionsHandling { - DevicePermissionsHandler.makeHandler() - } - - func titleForEmptyDataSet() -> String? { - if permissionHandler.contactsAuthorizationStatus == .authorized { - if self.searchController.isActive && self.searchController.searchBar.text?.isNotEmpty == true { - return Strings.Localizable.noResults - } else { - return Strings.Localizable.contactsEmptyStateTitle - } - } else { - return Strings.Localizable.enableAccessToYourAddressBook - } - } - - func buttonTitleForEmptyDataSet() -> String? { - if permissionHandler.contactsAuthorizationStatus != .authorized { - return Strings.Localizable.openSettings - } else { - return nil - } - } -} - -// MARK: - UISearchResultsUpdating - -extension ContactsPickerViewController: UISearchResultsUpdating { - - var isSearching: Bool { - searchController.isActive && !isSearchBarEmpty - } - - var isSearchBarEmpty: Bool { - searchController.searchBar.text?.isEmpty ?? true - } - - func updateSearchResults(for searchController: UISearchController) { - guard let searchString = searchController.searchBar.text else { return } - if searchController.isActive { - if searchString == "" { - searchingContacts.removeAll() - } else { - searchingContacts = contacts.filter({$0.name.contains(searchString)}) - } - } - tableView.reloadData() - } -} - -// MARK: - UISearchBarDelegate - -extension ContactsPickerViewController: UISearchBarDelegate { - func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { - searchingContacts.removeAll() - } - - func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool { - contacts.isNotEmpty - } -} - -// MARK: - UIAdaptivePresentationControllerDelegate - -extension ContactsPickerViewController: UIAdaptivePresentationControllerDelegate { - func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool { - selectedContacts.isEmpty - } - - func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) { - guard let sourceView = navigationController?.view else { return } - - let discardChangesActionSheet = UIAlertController().discardChanges(fromSourceView: sourceView, sourceRect: CGRect(x: 20, y: 20, width: 1, height: 1), withConfirmAction: { - self.dismiss(animated: true, completion: nil) - }) - present(discardChangesActionSheet, animated: true, completion: nil) - } -} diff --git a/iMEGA/My Account/Contacts/ContactsPicker/DeviceContactTableViewCell.swift b/iMEGA/My Account/Contacts/ContactsPicker/DeviceContactTableViewCell.swift deleted file mode 100644 index 112169e2ad..0000000000 --- a/iMEGA/My Account/Contacts/ContactsPicker/DeviceContactTableViewCell.swift +++ /dev/null @@ -1,35 +0,0 @@ - -import UIKit - -class DeviceContactTableViewCell: UITableViewCell { - - @IBOutlet weak var avatarImage: UIImageView! - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var subtitleLabel: UILabel! - @IBOutlet weak var descriptionLabel: UILabel! - @IBOutlet weak var selectionImage: UIImageView! - - override func awakeFromNib() { - super.awakeFromNib() - // Initialization code - } - - override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - - selectionImage.image = selected ? Asset.Images.Generic.thumbnailSelected.image : Asset.Images.Login.checkBoxUnselected.image - } - - func configure(for contact: DeviceContact) { - titleLabel.text = contact.name - subtitleLabel.text = contact.contactDetail - if let label = contact.contactDetailDescription { - descriptionLabel.text = CNLabeledValue.localizedString(forLabel: label) - } - if let imageData = contact.avatarData { - avatarImage.image = UIImage(data: imageData) - } else { - avatarImage.image = Asset.Images.MyAccount.iconContacts.image - } - } -} diff --git a/iMEGA/My Account/Contacts/ContactsPicker/DeviceContactsManager.swift b/iMEGA/My Account/Contacts/ContactsPicker/DeviceContactsManager.swift deleted file mode 100644 index 1f9f6d113b..0000000000 --- a/iMEGA/My Account/Contacts/ContactsPicker/DeviceContactsManager.swift +++ /dev/null @@ -1,101 +0,0 @@ - -import PhoneNumberKit -import UIKit - -protocol DeviceContactProtocol { - var name: String { get } - var avatarData: Data? { get } - var contactDetail: String { get } // Could be a phone number or a email address - var contactDetailDescription: String? { get } // Label to specify the contact detail (Home, Work, iPhone...) -} - -struct DeviceContact: DeviceContactProtocol, Hashable { - let name: String - let avatarData: Data? - let contactDetail: String - let contactDetailDescription: String? -} - -class DeviceContactsOperation: Operation { - - let keys: [String] - var fetchedContacts = [DeviceContact]() - - init(_ keys: [String]) { - self.keys = keys - } - - override func main() { - if isCancelled { - return - } - - let contactStore = CNContactStore() - - var fetchKeys = [CNContactFamilyNameKey, CNContactGivenNameKey, CNContactThumbnailImageDataKey] as [any CNKeyDescriptor] - - keys.forEach { (key) in - fetchKeys.append(key as any CNKeyDescriptor) - } - - let predicate = CNContact.predicateForContactsInContainer(withIdentifier: contactStore.defaultContainerIdentifier()) - do { - let phoneNumberKit = PhoneNumberKit() - - let contacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: fetchKeys) - for contact in contacts { - if isCancelled { - return - } - - let name = (contact.givenName + " " + contact.familyName) - - if contact.isKeyAvailable(CNContactPhoneNumbersKey) { - for phone in contact.phoneNumbers { - do { - let phoneNumber = try phoneNumberKit.parse(phone.value.stringValue) - let formatedNumber = phoneNumberKit.format(phoneNumber, toType: .e164) - - fetchedContacts.append(DeviceContact(name: name, avatarData: contact.thumbnailImageData, contactDetail: formatedNumber, contactDetailDescription: phone.label)) - } catch { - MEGALogError("Device contact number parser error " + phone.value.stringValue) - } - } - } - - if contact.isKeyAvailable(CNContactEmailAddressesKey) { - contact.emailAddresses.forEach { (email) in - if email.value.mnz_isValidEmail() { - fetchedContacts.append(DeviceContact(name: name, avatarData: contact.thumbnailImageData, contactDetail: String(email.value), contactDetailDescription: email.label)) - } else { - MEGALogError("Device contact email not valid: " + String(email.value)) - } - } - } - } - } catch { - MEGALogError("Error fetching user contacts: " + error.localizedDescription) - } - } -} - -class DeviceContactsManager: NSObject { - - static let shared = DeviceContactsManager() - - private let operationQueue: OperationQueue = { - var queue = OperationQueue() - queue.name = "DeviceContactsQueue" - queue.maxConcurrentOperationCount = 1 - queue.qualityOfService = .background - return queue - }() - - func cancelDeviceContactsOperation(_ operation: DeviceContactsOperation) { - operation.cancel() - } - - func addGetDeviceContactsOperation(_ operation: DeviceContactsOperation) { - operationQueue.addOperation(operation) - } -} diff --git a/iMEGA/My Account/Contacts/ContactsViewController+Additions.swift b/iMEGA/My Account/Contacts/ContactsViewController+Additions.swift index ab9fcda9f5..78f8dde59b 100644 --- a/iMEGA/My Account/Contacts/ContactsViewController+Additions.swift +++ b/iMEGA/My Account/Contacts/ContactsViewController+Additions.swift @@ -1,3 +1,6 @@ +import Contacts +import MEGADomain +import MEGAL10n import MEGAPresentation import SwiftUI @@ -83,4 +86,10 @@ extension ContactsViewController { sdk: MEGASdk.shared ) } + + @objc + func extractEmails(_ contacts: [CNContact]) -> [NSString] { + let emails = contacts.extractEmails() + return emails.map { NSString(string: $0) } + } } diff --git a/iMEGA/My Account/Contacts/ContactsViewController.m b/iMEGA/My Account/Contacts/ContactsViewController.m index 4ebc214fdf..4b2cf69d14 100644 --- a/iMEGA/My Account/Contacts/ContactsViewController.m +++ b/iMEGA/My Account/Contacts/ContactsViewController.m @@ -1,4 +1,3 @@ - #import "ContactsViewController.h" #import "SVProgressHUD.h" @@ -28,10 +27,12 @@ #import "EmptyStateView.h" #import "ItemListViewController.h" #import "NSArray+MNZCategory.h" + +@import MEGAL10nObjc; @import MEGAUIKit; @import MEGASDKRepo; -@interface ContactsViewController () +@interface ContactsViewController () @property (weak, nonatomic) IBOutlet UIView *itemListView; @@ -146,11 +147,11 @@ - (void)viewDidLoad { [MEGASdkManager.sharedMEGASdk addMEGARequestDelegate:self]; if (self.contactsMode == ContactsModeChatNamingGroup) { - self.enterGroupNameTextField.placeholder = NSLocalizedString(@"Enter group name", @"Title of the dialog shown when the user it is creating a chat link and the chat has not title"); + self.enterGroupNameTextField.placeholder = LocalizedString(@"Enter group name", @"Title of the dialog shown when the user it is creating a chat link and the chat has not title"); self.enterGroupNameTextFieldDelegate = EnterGroupNameTextFieldDelegate.new; self.enterGroupNameTextField.delegate = self.enterGroupNameTextFieldDelegate; [self.allowNonHostToAddParticipantsSwitch setOn:YES]; - self.allowNonHostToAddParticipantsLabel.text = NSLocalizedString(@"meetings.addContacts.allowNonHost.message", @"Message to allow non host to add contacts in the group chat and meeting"); + self.allowNonHostToAddParticipantsLabel.text = LocalizedString(@"meetings.addContacts.allowNonHost.message", @"Message to allow non host to add contacts in the group chat and meeting"); } [self updateAppearance]; @@ -201,7 +202,7 @@ - (void)viewDidAppear:(BOOL)animated { if (self.contactsMode == ContactsModeDefault) { MEGAContactRequestList *incomingContactsLists = [[MEGASdkManager sharedMEGASdk] incomingContactRequests]; - if (!self.avoidPresentIncomingPendingContactRequests && incomingContactsLists.size.intValue > 0) { + if (!self.avoidPresentIncomingPendingContactRequests && incomingContactsLists.size > 0) { ContactRequestsViewController *contactRequestsVC = [[UIStoryboard storyboardWithName:@"Contacts" bundle:nil] instantiateViewControllerWithIdentifier:@"ContactsRequestsViewControllerID"]; [self.navigationController pushViewController:contactRequestsVC animated:YES]; @@ -281,16 +282,16 @@ - (void)setupContacts { } case ContactsModeShareFoldersWith: { - self.cancelBarButtonItem.title = NSLocalizedString(@"cancel", nil); + self.cancelBarButtonItem.title = LocalizedString(@"cancel", @""); [self.cancelBarButtonItem setTitleTextAttributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleBody]} forState:UIControlStateNormal]; self.navigationItem.leftBarButtonItems = @[self.cancelBarButtonItem]; - self.shareFolderWithBarButtonItem.title = NSLocalizedString(@"next", @"Button title which, if tapped, will show the share access level selection to proceed to sharing"); + self.shareFolderWithBarButtonItem.title = LocalizedString(@"next", @"Button title which, if tapped, will show the share access level selection to proceed to sharing"); [self.shareFolderWithBarButtonItem setTitleTextAttributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleBody weight:UIFontWeightMedium]} forState:UIControlStateNormal]; self.navigationItem.rightBarButtonItems = @[self.shareFolderWithBarButtonItem]; self.shareFolderWithBarButtonItem.enabled = NO; - self.insertAnEmailBarButtonItem.title = NSLocalizedString(@"inviteContact", @"Text shown when the user tries to make a call and the receiver is not a contact"); + self.insertAnEmailBarButtonItem.title = LocalizedString(@"inviteContact", @"Text shown when the user tries to make a call and the receiver is not a contact"); [self.insertAnEmailBarButtonItem setTitleTextAttributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleBody]} forState:UIControlStateNormal]; UIBarButtonItem *flexibleItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; @@ -303,11 +304,11 @@ - (void)setupContacts { } case ContactsModeFolderSharedWith: { - self.editBarButtonItem.title = NSLocalizedString(@"select", @"Caption of a button to select files"); + self.editBarButtonItem.title = LocalizedString(@"select", @"Caption of a button to select files"); self.navigationItem.rightBarButtonItems = @[self.editBarButtonItem]; UIBarButtonItem *flexibleItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; - self.deleteBarButtonItem.title = NSLocalizedString(@"remove", @"Title for the action that allows to remove a file or folder"); + self.deleteBarButtonItem.title = LocalizedString(@"remove", @"Title for the action that allows to remove a file or folder"); [self.deleteBarButtonItem setTitleTextAttributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleBody]} forState:UIControlStateNormal]; self.toolbar.items = @[flexibleItem, self.deleteBarButtonItem]; [self setupContactsNotVerifiedHeader]; @@ -315,12 +316,12 @@ - (void)setupContacts { } case ContactsModeChatStartConversation: { - self.cancelBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.cancelBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); self.navigationItem.rightBarButtonItems = @[self.cancelBarButtonItem]; if (self.visibleUsersArray.count == 0) { - self.noContactsLabel.text = NSLocalizedString(@"contactsEmptyState_title", @"Title shown when the Contacts section is empty, when you have not added any contact."); - self.noContactsDescriptionLabel.text = NSLocalizedString(@"Invite contacts and start chatting securely with MEGA’s encrypted chat.", @"Text encouraging the user to invite contacts to MEGA"); - self.inviteContactButton.titleLabel.text = NSLocalizedString(@"inviteContact", @"Text shown when the user tries to make a call and the receiver is not a contact"); + self.noContactsLabel.text = LocalizedString(@"contactsEmptyState_title", @"Title shown when the Contacts section is empty, when you have not added any contact."); + self.noContactsDescriptionLabel.text = LocalizedString(@"Invite contacts and start chatting securely with MEGA’s encrypted chat.", @"Text encouraging the user to invite contacts to MEGA"); + self.inviteContactButton.titleLabel.text = LocalizedString(@"inviteContact", @"Text shown when the user tries to make a call and the receiver is not a contact"); } break; } @@ -329,8 +330,8 @@ - (void)setupContacts { case ContactsModeInviteParticipants: case ContactsModeScheduleMeeting: case ContactsModeChatAttachParticipant: { - self.cancelBarButtonItem.title = NSLocalizedString(@"cancel", nil); - self.addParticipantBarButtonItem.title = (self.contactsMode == ContactsModeInviteParticipants) ? NSLocalizedString(@"invite", nil) : (self.contactsMode == ContactsModeScheduleMeeting) ? NSLocalizedString(@"next", nil) : NSLocalizedString(@"ok", nil); + self.cancelBarButtonItem.title = LocalizedString(@"cancel", @""); + self.addParticipantBarButtonItem.title = (self.contactsMode == ContactsModeInviteParticipants) ? LocalizedString(@"invite", @"") : (self.contactsMode == ContactsModeScheduleMeeting) ? LocalizedString(@"next", @"") : LocalizedString(@"ok", @""); [self setTableViewEditing:YES animated:NO]; self.navigationItem.leftBarButtonItem = self.cancelBarButtonItem; self.navigationItem.rightBarButtonItems = @[self.addParticipantBarButtonItem]; @@ -341,9 +342,9 @@ - (void)setupContacts { case ContactsModeChatCreateGroup: { [self setTableViewEditing:YES animated:NO]; - self.createGroupBarButtonItem.title = NSLocalizedString(@"next", nil); + self.createGroupBarButtonItem.title = LocalizedString(@"next", @""); [self.createGroupBarButtonItem setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleBody weight:UIFontWeightMedium], NSFontAttributeName, nil] forState:UIControlStateNormal]; - self.cancelBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.cancelBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); self.navigationItem.rightBarButtonItems = @[self.createGroupBarButtonItem]; self.navigationItem.leftBarButtonItem = self.cancelBarButtonItem; self.navigationController.toolbarHidden = YES; @@ -352,10 +353,10 @@ - (void)setupContacts { case ContactsModeChatNamingGroup: { - self.createGroupBarButtonItem.title = NSLocalizedString(@"createFolderButton", nil); - self.encryptedKeyRotationLabel.text = NSLocalizedString(@"Encrypted Key Rotation", @"Label in a cell where you can enable the 'Encrypted Key Rotation'"); - self.getChatLinkLabel.text = NSLocalizedString(@"Get Chat Link", @"Label in a cell where you can get the chat link"); - self.keyRotationFooterLabel.text = NSLocalizedString(@"Key rotation is slightly more secure, but does not allow you to create a chat link and new participants will not see past messages.", @"Footer text to explain what means 'Encrypted Key Rotation'"); + self.createGroupBarButtonItem.title = LocalizedString(@"createFolderButton", @""); + self.encryptedKeyRotationLabel.text = LocalizedString(@"Encrypted Key Rotation", @"Label in a cell where you can enable the 'Encrypted Key Rotation'"); + self.getChatLinkLabel.text = LocalizedString(@"Get Chat Link", @"Label in a cell where you can get the chat link"); + self.keyRotationFooterLabel.text = LocalizedString(@"Key rotation is slightly more secure, but does not allow you to create a chat link and new participants will not see past messages.", @"Footer text to explain what means 'Encrypted Key Rotation'"); [self.createGroupBarButtonItem setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleBody weight:UIFontWeightMedium], NSFontAttributeName, nil] forState:UIControlStateNormal]; self.navigationItem.rightBarButtonItems = @[self.createGroupBarButtonItem]; [self.tableView setEditing:NO animated:YES]; @@ -369,7 +370,7 @@ - (void)setupContacts { - (void)showInviteToolbarButton { UIBarButtonItem *flexibleItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; - self.inviteBarButtonItem.title = NSLocalizedString(@"inviteContact", nil); + self.inviteBarButtonItem.title = LocalizedString(@"inviteContact", @""); [self.inviteBarButtonItem setTitleTextAttributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleBody]} forState:UIControlStateNormal]; self.navigationController.topViewController.toolbarItems = @[flexibleItem, self.inviteBarButtonItem]; [self.navigationController setToolbarHidden:NO]; @@ -617,16 +618,16 @@ - (ActionSheetViewController *)prepareShareFolderAlertControllerFromSender:(id)s __weak __typeof__(self) weakSelf = self; NSMutableArray *actions = NSMutableArray.new; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"fullAccess", @"Permissions given to the user you share your folder with") detail:nil image:[UIImage imageNamed:@"fullAccessPermissions"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"fullAccess", @"Permissions given to the user you share your folder with") detail:nil image:[UIImage imageNamed:@"fullAccessPermissions"] style:UIAlertActionStyleDefault actionHandler:^{ [weakSelf shareNodesWithLevel:MEGAShareTypeAccessFull]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"readAndWrite", @"Permissions given to the user you share your folder with") detail:nil image:[UIImage imageNamed:@"readWritePermissions"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"readAndWrite", @"Permissions given to the user you share your folder with") detail:nil image:[UIImage imageNamed:@"readWritePermissions"] style:UIAlertActionStyleDefault actionHandler:^{ [weakSelf shareNodesWithLevel:MEGAShareTypeAccessReadWrite]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"readOnly", @"Permissions given to the user you share your folder with") detail:nil image:[UIImage imageNamed:@"readPermissions"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"readOnly", @"Permissions given to the user you share your folder with") detail:nil image:[UIImage imageNamed:@"readPermissions"] style:UIAlertActionStyleDefault actionHandler:^{ [weakSelf shareNodesWithLevel:MEGAShareTypeAccessRead]; }]]; - ActionSheetViewController *shareFolderActionSheet = [ActionSheetViewController.alloc initWithActions:actions headerTitle:NSLocalizedString(@"permissions", @"Title of the view that shows the kind of permissions (Read Only, Read & Write or Full Access) that you can give to a shared folder") dismissCompletion:nil sender:sender]; + ActionSheetViewController *shareFolderActionSheet = [ActionSheetViewController.alloc initWithActions:actions headerTitle:LocalizedString(@"permissions", @"Title of the view that shows the kind of permissions (Read Only, Read & Write or Full Access) that you can give to a shared folder") dismissCompletion:nil sender:sender]; return shareFolderActionSheet; } @@ -712,7 +713,7 @@ - (void)setTableViewEditing:(BOOL)editing animated:(BOOL)animated { [self.tableView setEditing:editing animated:animated]; if (editing) { - self.editBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.editBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); [self.addBarButtonItem setEnabled:NO]; UITabBar *tabBar = self.tabBarController.tabBar; @@ -743,7 +744,7 @@ - (void)setTableViewEditing:(BOOL)editing animated:(BOOL)animated { cell.selectedBackgroundView = view; } } else { - self.editBarButtonItem.title = NSLocalizedString(@"select", @"Caption of a button to select files"); + self.editBarButtonItem.title = LocalizedString(@"select", @"Caption of a button to select files"); self.selectedUsersArray = nil; [self.addBarButtonItem setEnabled:YES]; @@ -855,7 +856,8 @@ - (NSIndexPath *)indexPathForUser:(MEGAUser *)user { - (void)updatePendingContactRequestsLabel { if (self.contactsMode == ContactsModeDefault) { MEGAContactRequestList *incomingContactsLists = MEGASdkManager.sharedMEGASdk.incomingContactRequests; - self.contactsTableViewHeader.requestsDetailLabel.text = incomingContactsLists.size.intValue == 0 ? @"" : incomingContactsLists.size.stringValue; + self.contactsTableViewHeader.requestsDetailLabel.text = incomingContactsLists.size == 0 ? @"" : [NSString stringWithFormat:@"%ld", (long)incomingContactsLists.size]; + } } @@ -1004,8 +1006,11 @@ - (void)joinMeeting { } - (void)showEmailContactPicker { - MEGANavigationController *contactsPickerNavigation = [MEGANavigationController.alloc initWithRootViewController:[ContactsPickerViewController instantiateWithContactKeys:@[CNContactEmailAddressesKey] delegate:self]]; - [self presentViewController:contactsPickerNavigation animated:YES completion:nil]; + CNContactPickerViewController *contactPickerController = [[CNContactPickerViewController alloc] init]; + contactPickerController.predicateForEnablingContact = [NSPredicate predicateWithFormat:@"emailAddresses.@count > 0"]; + contactPickerController.delegate = self; + + [self presentViewController:contactPickerController animated:YES completion:nil]; } - (void)selectUser:(MEGAUser *)user { @@ -1139,23 +1144,23 @@ - (IBAction)addContact:(id)sender { } if (self.contactsMode == ContactsModeShareFoldersWith) { - UIAlertController *addContactAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"inviteContact", @"Text shown when the user tries to make a call and the receiver is not a contact") message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - [addContactAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *addContactAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"inviteContact", @"Text shown when the user tries to make a call and the receiver is not a contact") message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + [addContactAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - UIAlertAction *addFromEmailAlertAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"addFromEmail", @"Item menu option to add a contact writting his/her email") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - UIAlertController *addContactFromEmailAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email") message:nil preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *addFromEmailAlertAction = [UIAlertAction actionWithTitle:LocalizedString(@"addFromEmail", @"Item menu option to add a contact writting his/her email") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertController *addContactFromEmailAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"addContact", @"Alert title shown when you select to add a contact inserting his/her email") message:nil preferredStyle:UIAlertControllerStyleAlert]; [addContactFromEmailAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.placeholder = NSLocalizedString(@"contactEmail", @"Clue text to help the user know what should write there. In this case the contact email you want to add to your contacts list"); + textField.placeholder = LocalizedString(@"contactEmail", @"Clue text to help the user know what should write there. In this case the contact email you want to add to your contacts list"); [textField addTarget:self action:@selector(addContactAlertTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { return (!textField.text.mnz_isEmpty && textField.text.mnz_isValidEmail); }; }]; - [addContactFromEmailAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [addContactFromEmailAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - UIAlertAction *addContactAlertAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"addContactButton", @"Button title to 'Add' the contact to your contacts list") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertAction *addContactAlertAction = [UIAlertAction actionWithTitle:LocalizedString(@"addContactButton", @"Button title to 'Add' the contact to your contacts list") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { UITextField *textField = addContactFromEmailAlertController.textFields.firstObject; if (self.contactsMode == ContactsModeShareFoldersWith) { [self inviteEmailToShareFolder:textField.text]; @@ -1174,7 +1179,7 @@ - (IBAction)addContact:(id)sender { }]; [addContactAlertController addAction:addFromEmailAlertAction]; - UIAlertAction *addFromContactsAlertAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"addFromContacts", @"Item menu option to add a contact through your device app Contacts") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertAction *addFromContactsAlertAction = [UIAlertAction actionWithTitle:LocalizedString(@"addFromContacts", @"Item menu option to add a contact through your device app Contacts") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { if (self.presentedViewController != nil) { [self.presentedViewController dismissViewControllerAnimated:YES completion:^{ [self showEmailContactPicker]; @@ -1185,7 +1190,7 @@ - (IBAction)addContact:(id)sender { }]; [addContactAlertController addAction:addFromContactsAlertAction]; - UIAlertAction *scanCodeAlertAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"scanCode", @"Segmented control title for view that allows the user to scan QR codes. String as short as possible.") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertAction *scanCodeAlertAction = [UIAlertAction actionWithTitle:LocalizedString(@"scanCode", @"Segmented control title for view that allows the user to scan QR codes. String as short as possible.") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { ContactLinkQRViewController *contactLinkVC = [[UIStoryboard storyboardWithName:@"ContactLinkQR" bundle:nil] instantiateViewControllerWithIdentifier:@"ContactLinkQRViewControllerID"]; contactLinkVC.scanCode = YES; contactLinkVC.modalPresentationStyle = UIModalPresentationFullScreen; @@ -1286,8 +1291,8 @@ - (IBAction)createGroupAction:(UIBarButtonItem *)sender { [self.navigationController pushViewController:contactsVC animated:YES]; } else { if (!self.isKeyRotationEnabled && self.chatLinkSwitch.isOn && self.enterGroupNameTextField.text.mnz_isEmpty) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Chat Link", @"Label shown in a cell where you can enable a switch to get a chat link") message:NSLocalizedString(@"To create a chat link you must name the group.", @"Alert message to advice the users that to generate a chat link they need enter a group name for the chat") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"Chat Link", @"Label shown in a cell where you can enable a switch to get a chat link") message:LocalizedString(@"To create a chat link you must name the group.", @"Alert message to advice the users that to generate a chat link they need enter a group name for the chat") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self.enterGroupNameTextField becomeFirstResponder]; }]]; [self presentViewController:alertController animated:YES completion:nil]; @@ -1553,7 +1558,7 @@ - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger if (section == 0) { if (self.recentlyAddedUsersArray.count > 0) { headerView.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]; - [headerView configureWithTitle:NSLocalizedString(@"Recently Added", @"Label for any ‘Recently Added’ button, link, text, title, etc. On iOS is used on a section that shows the 'Recently Added' contacts") topDistance:17.0 isTopSeparatorVisible:YES isBottomSeparatorVisible:YES]; + [headerView configureWithTitle:LocalizedString(@"Recently Added", @"Label for any ‘Recently Added’ button, link, text, title, etc. On iOS is used on a section that shows the 'Recently Added' contacts") topDistance:17.0 isTopSeparatorVisible:YES isBottomSeparatorVisible:YES]; return headerView; } @@ -1570,10 +1575,10 @@ - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger if (self.contactsMode == ContactsModeFolderSharedWith) { if (section == 0) { - [headerView configureWithTitle:NSLocalizedString(@"sharedWith", @"Title of the view where you see with who you have shared a folder") topDistance:30.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; + [headerView configureWithTitle:LocalizedString(@"sharedWith", @"Title of the view where you see with who you have shared a folder") topDistance:30.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; } else if (section == 1 && self.pendingShareUsersArray.count > 0) { - [headerView configureWithTitle:NSLocalizedString(@"pending", @"Label shown when a contact request is pending") topDistance:self.pendingShareUsersArray.count > 0 ? 30.0 : 1.0 isTopSeparatorVisible:YES isBottomSeparatorVisible:YES]; + [headerView configureWithTitle:LocalizedString(@"pending", @"Label shown when a contact request is pending") topDistance:self.pendingShareUsersArray.count > 0 ? 30.0 : 1.0 isTopSeparatorVisible:YES isBottomSeparatorVisible:YES]; } else { [headerView configureWithTitle:nil topDistance:1.00 isTopSeparatorVisible:NO isBottomSeparatorVisible:NO]; } @@ -1585,12 +1590,12 @@ - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger || self.contactsMode == ContactsModeInviteParticipants || self.contactsMode == ContactsModeScheduleMeeting)) { headerView.contentView.backgroundColor = UIColor.mnz_background; - [headerView configureWithTitle:NSLocalizedString(@"contactsTitle", @"Title of the Contacts section") topDistance:14.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; + [headerView configureWithTitle:LocalizedString(@"contactsTitle", @"Title of the Contacts section") topDistance:14.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; return headerView; } if (section == 0 && self.contactsMode == ContactsModeChatNamingGroup) { - [headerView configureWithTitle:NSLocalizedString(@"participants", @"Label to describe the section where you can see the participants of a group chat") topDistance:24.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; + [headerView configureWithTitle:LocalizedString(@"participants", @"Label to describe the section where you can see the participants of a group chat") topDistance:24.0 isTopSeparatorVisible:NO isBottomSeparatorVisible:YES]; headerView.marginViewHeightConstraint.constant = 0.0; return headerView; } @@ -1599,11 +1604,11 @@ - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger return headerView; } if (section == 1 && self.contactsMode == ContactsModeChatStartConversation) { - [headerView configureWithTitle:NSLocalizedString(@"Recents", @"Title for the recents section") topDistance:10 isTopSeparatorVisible:YES isBottomSeparatorVisible:YES]; + [headerView configureWithTitle:LocalizedString(@"Recents", @"Title for the recents section") topDistance:10 isTopSeparatorVisible:YES isBottomSeparatorVisible:YES]; return headerView; } if ((section == 2 && self.contactsMode == ContactsModeChatStartConversation) || (section == 1 && self.contactsMode > ContactsModeChatStartConversation)) { - [headerView configureWithTitle:NSLocalizedString(@"contactsTitle", @"Title of the Contacts section") topDistance:10 isTopSeparatorVisible:YES isBottomSeparatorVisible:YES]; + [headerView configureWithTitle:LocalizedString(@"contactsTitle", @"Title of the Contacts section") topDistance:10 isTopSeparatorVisible:YES isBottomSeparatorVisible:YES]; return headerView; } @@ -1683,11 +1688,11 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath } } else { if (!tableView.isEditing) { - UIAlertController *removePendingShareAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"removeUserTitle", @"Alert title shown when you want to remove one or more contacts") message:self.pendingShareUsersArray[indexPath.row].user preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *removePendingShareAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"removeUserTitle", @"Alert title shown when you want to remove one or more contacts") message:self.pendingShareUsersArray[indexPath.row].user preferredStyle:UIAlertControllerStyleAlert]; - [removePendingShareAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [removePendingShareAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - [removePendingShareAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [removePendingShareAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { MEGAShareRequestDelegate *shareRequestDelegate = [MEGAShareRequestDelegate.alloc initToChangePermissionsWithNumberOfRequests:1 completion:^{ [self setTableViewEditing:NO animated:NO]; [self reloadUI]; @@ -1843,9 +1848,11 @@ - (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexP [self setTableViewEditing:NO animated:YES]; } -#pragma mark - ContactsPickerViewControllerDelegate +#pragma mark - CNContactPickerDelegate + +-(void)contactPicker:(CNContactPickerViewController *)picker didSelectContacts:(NSArray *)contacts { + NSArray * values = [self extractEmails:contacts]; -- (void)contactsPicker:(ContactsPickerViewController *)contactsPicker didSelectContacts:(NSArray *)values { if (self.childViewControllers.count == 0) { [self insertItemListSubviewWithCompletion:^{ for (NSString *email in values) { @@ -1876,14 +1883,14 @@ - (NSString *)titleForEmptyState { if (self.contactsMode != ContactsModeChatNamingGroup) { if (self.searchController.isActive ) { if (self.searchController.searchBar.text.length > 0) { - text = NSLocalizedString(@"noResults", @"Title shown when you make a search and there is 'No Results'"); + text = LocalizedString(@"noResults", @"Title shown when you make a search and there is 'No Results'"); } } else { - text = NSLocalizedString(@"contactsEmptyState_title", @"Title shown when the Contacts section is empty, when you have not added any contact."); + text = LocalizedString(@"contactsEmptyState_title", @"Title shown when the Contacts section is empty, when you have not added any contact."); } } } else { - text = NSLocalizedString(@"noInternetConnection", @"No Internet Connection"); + text = LocalizedString(@"noInternetConnection", @"No Internet Connection"); } return text; @@ -1892,7 +1899,7 @@ - (NSString *)titleForEmptyState { - (NSString *)descriptionForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); + text = LocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); } return text; @@ -1924,9 +1931,9 @@ - (nullable NSString *)buttonTitleForEmptyState { NSString *text = @""; if (MEGAReachabilityManager.isReachable && !self.searchController.isActive) { - text = NSLocalizedString(@"inviteContact", @"Text shown when the user tries to make a call and the receiver is not a contact"); + text = LocalizedString(@"inviteContact", @"Text shown when the user tries to make a call and the receiver is not a contact"); } else if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); + text = LocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); } return text; diff --git a/iMEGA/My Account/Contacts/Invite Contact/EnterEmailViewController.swift b/iMEGA/My Account/Contacts/Invite Contact/EnterEmailViewController.swift index da7122c524..e882b559c1 100644 --- a/iMEGA/My Account/Contacts/Invite Contact/EnterEmailViewController.swift +++ b/iMEGA/My Account/Contacts/Invite Contact/EnterEmailViewController.swift @@ -1,4 +1,6 @@ import ContactsUI +import MEGADomain +import MEGAL10n import UIKit import WSTagsField @@ -14,6 +16,12 @@ class EnterEmailViewController: UIViewController { @IBOutlet weak var tagsFieldButton: UIButton! @IBOutlet weak var inviteContactsButtonBottomConstraint: NSLayoutConstraint! + private let contactPickerViewController: CNContactPickerViewController = { + let controller = CNContactPickerViewController() + controller.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0") + return controller + }() + // MARK: Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -29,6 +37,7 @@ class EnterEmailViewController: UIViewController { updateAppearance() navigationController?.presentationController?.delegate = self + contactPickerViewController.delegate = self } override func viewDidAppear(_ animated: Bool) { @@ -150,6 +159,19 @@ class EnterEmailViewController: UIViewController { configureTagFieldEvents() } + private func updateUIOnEmailPickedFromContacts(_ email: String) { + tagsField.addTag(email) + + instructionsLabel.text = Strings.Localizable.tapSpaceToEnterMultipleEmails + instructionsLabel.textColor = UIColor.mnz_secondaryGray(for: self.traitCollection) + + if tagsField.tags.isEmpty { + disableInviteContactsButton() + } else { + enableInviteContactsButton() + } + } + // MARK: Actions @IBAction func inviteContactsTapped(_ sender: UIButton) { if let text = tagsField.text, text.mnz_isValidEmail() { @@ -170,7 +192,7 @@ class EnterEmailViewController: UIViewController { weakSelf?.navigationController?.popViewController(animated: true) } tagsField.tags.forEach { (tag) in - MEGASdkManager.sharedMEGASdk().inviteContact(withEmail: tag.text, message: "", action: MEGAInviteAction.add, delegate: inviteContactRequestDelegate) + MEGASdk.shared.inviteContact(withEmail: tag.text, message: "", action: MEGAInviteAction.add, delegate: inviteContactRequestDelegate) } tagsField.textField.resignFirstResponder() @@ -187,27 +209,16 @@ class EnterEmailViewController: UIViewController { } private func showContactsPicker() { - let contactsPickerNavigation = MEGANavigationController.init(rootViewController: ContactsPickerViewController.instantiate(withContactKeys: [CNContactEmailAddressesKey], delegate: self)) - present(contactsPickerNavigation, animated: true, completion: nil) + present(contactPickerViewController, animated: true, completion: nil) } } -// MARK: - ContactsPickerViewControllerDelegate +// MARK: - CNContactPickerDelegate -extension EnterEmailViewController: ContactsPickerViewControllerDelegate { - func contactsPicker(_ contactsPicker: ContactsPickerViewController, didSelectContacts values: [String]) { - values.forEach { (email) in - tagsField.addTag(email) - - instructionsLabel.text = Strings.Localizable.tapSpaceToEnterMultipleEmails - instructionsLabel.textColor = UIColor.mnz_secondaryGray(for: self.traitCollection) - - if tagsField.tags.isEmpty { - disableInviteContactsButton() - } else { - enableInviteContactsButton() - } - } +extension EnterEmailViewController: CNContactPickerDelegate { + func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) { + let emails = contacts.extractEmails() + emails.forEach(updateUIOnEmailPickedFromContacts(_:)) } } diff --git a/iMEGA/My Account/Contacts/Invite Contact/InviteContactViewController.swift b/iMEGA/My Account/Contacts/Invite Contact/InviteContactViewController.swift index 42d1dd04b5..7fa4fa1163 100644 --- a/iMEGA/My Account/Contacts/Invite Contact/InviteContactViewController.swift +++ b/iMEGA/My Account/Contacts/Invite Contact/InviteContactViewController.swift @@ -1,5 +1,7 @@ import Contacts import ContactsUI +import MEGADomain +import MEGAL10n import MEGAPermissions import MessageUI import UIKit @@ -20,6 +22,12 @@ class InviteContactViewController: UIViewController { @IBOutlet weak var scanQrCodeSeparatorView: UIView! @IBOutlet weak var moreLabel: UILabel! + private let contactPickerViewController: CNContactPickerViewController = { + let controller = CNContactPickerViewController() + controller.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.@count > 0") + return controller + }() + // MARK: Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -34,13 +42,16 @@ class InviteContactViewController: UIViewController { guard let base64Handle = MEGASdk.base64Handle(forHandle: request.nodeHandle) else { return } self.userLink = String(format: "https://mega.nz/C!%@", base64Handle) } - MEGASdkManager.sharedMEGASdk().contactLinkCreateRenew(false, delegate: contactLinkCreateDelegate) + + MEGASdk.shared.contactLinkCreateRenew(false, delegate: contactLinkCreateDelegate) if !MFMessageComposeViewController.canSendText() { addFromContactsLabel.textColor = UIColor.mnz_secondaryGray(for: self.traitCollection) } updateAppearance() + + contactPickerViewController.delegate = self } override func viewWillAppear(_ animated: Bool) { @@ -62,7 +73,7 @@ class InviteContactViewController: UIViewController { // MARK: - Private - func updateAppearance() { + private func updateAppearance() { mainView.backgroundColor = (presentingViewController == nil) ? .mnz_backgroundGrouped(for: traitCollection) : .mnz_backgroundGroupedElevated(traitCollection) let separatorColor = UIColor.mnz_separator(for: self.traitCollection) @@ -71,14 +82,21 @@ class InviteContactViewController: UIViewController { scanQrCodeSeparatorView.backgroundColor = separatorColor } + private func presentComposeControllerForPhoneNumbers(_ phoneNumbers: [String]) { + let composeVC = MFMessageComposeViewController() + composeVC.messageComposeDelegate = self + composeVC.recipients = phoneNumbers + composeVC.body = Strings.Localizable.Contact.Invite.message + " " + self.userLink + present(composeVC, animated: true, completion: nil) + } + // MARK: Actions @IBAction func addFromContactsButtonTapped(_ sender: Any) { guard MFMessageComposeViewController.canSendText() else { return } - - let contactsPickerNavigation = MEGANavigationController.init(rootViewController: ContactsPickerViewController.instantiate(withContactKeys: [CNContactPhoneNumbersKey], delegate: self)) - present(contactsPickerNavigation, animated: true, completion: nil) + + present(contactPickerViewController, animated: true, completion: nil) } @IBAction func enterEmailButtonTapped(_ sender: Any) { @@ -119,15 +137,10 @@ extension InviteContactViewController: MFMessageComposeViewControllerDelegate { } } -// MARK: - ContactsPickerViewControllerDelegate - -extension InviteContactViewController: ContactsPickerViewControllerDelegate { - func contactsPicker(_ contactsPicker: ContactsPickerViewController, didSelectContacts values: [String]) { - let composeVC = MFMessageComposeViewController() - composeVC.messageComposeDelegate = self - composeVC.recipients = values - composeVC.body = Strings.Localizable.Contact.Invite.message + " " + self.userLink - self.present(composeVC, animated: true, completion: nil) +extension InviteContactViewController: CNContactPickerDelegate { + func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) { + let phoneNumbers = contacts.extractPhoneNumbers() + presentComposeControllerForPhoneNumbers(phoneNumbers) } } diff --git a/iMEGA/My Account/Contacts/ItemList/ItemListViewController.h b/iMEGA/My Account/Contacts/ItemList/ItemListViewController.h index 11c0888b5b..163eff045e 100644 --- a/iMEGA/My Account/Contacts/ItemList/ItemListViewController.h +++ b/iMEGA/My Account/Contacts/ItemList/ItemListViewController.h @@ -1,4 +1,3 @@ - #import #import "ItemListModel.h" diff --git a/iMEGA/My Account/Contacts/MeetingAlreadyExistsAlert.swift b/iMEGA/My Account/Contacts/MeetingAlreadyExistsAlert.swift index 064011168a..e0d1c4d90a 100644 --- a/iMEGA/My Account/Contacts/MeetingAlreadyExistsAlert.swift +++ b/iMEGA/My Account/Contacts/MeetingAlreadyExistsAlert.swift @@ -1,3 +1,4 @@ +import MEGAL10n @objc final class MeetingAlreadyExistsAlert: NSObject { @objc static func show(presenter: UIViewController) { diff --git a/iMEGA/My Account/Contacts/NicknameViewController.swift b/iMEGA/My Account/Contacts/NicknameViewController.swift index e9bc452fb8..fbb9d9f583 100644 --- a/iMEGA/My Account/Contacts/NicknameViewController.swift +++ b/iMEGA/My Account/Contacts/NicknameViewController.swift @@ -1,3 +1,5 @@ +import MEGAL10n +import MEGASDKRepo import UIKit class NicknameViewController: UIViewController { @@ -87,11 +89,14 @@ class NicknameViewController: UIViewController { guard let user = user else { return } - - let genericRequestDelegate = MEGAGenericRequestDelegate { request, error in + + SVProgressHUD.setDefaultMaskType(.clear) + SVProgressHUD.show() + MEGASdk.shared.setUserAlias(nickname, forHandle: user.handle, delegate: RequestDelegate { [weak self] (result) in SVProgressHUD.dismiss() - - if error.type == .apiOk { + guard let self else { return } + switch result { + case .success: if let user = self.user { NotificationCenter.default.post(name: NSNotification.Name.MEGContactNicknameChange, object: nil, @@ -100,17 +105,13 @@ class NicknameViewController: UIViewController { user.mnz_nickname = nickname } - self.updateHandler(withNickname: nickname) - } else { - SVProgressHUD.showError(withStatus: request.requestString + " " + NSLocalizedString(error.name, comment: "")) + updateHandler(withNickname: nickname) + case .failure(let error): + SVProgressHUD.showError(withStatus: Strings.localized(error.name, comment: "")) } - - self.dismissViewController() - } - - SVProgressHUD.setDefaultMaskType(.clear) - SVProgressHUD.show() - MEGASdkManager.sharedMEGASdk().setUserAlias(nickname, forHandle: user.handle, delegate: genericRequestDelegate) + + dismissViewController() + }) } private func dismissViewController() { diff --git a/iMEGA/My Account/Contacts/VerifyCredentialsViewController+Additions.swift b/iMEGA/My Account/Contacts/VerifyCredentialsViewController+Additions.swift index 2836514432..77d721d1b6 100644 --- a/iMEGA/My Account/Contacts/VerifyCredentialsViewController+Additions.swift +++ b/iMEGA/My Account/Contacts/VerifyCredentialsViewController+Additions.swift @@ -1,3 +1,4 @@ +import MEGAL10n extension VerifyCredentialsViewController { diff --git a/iMEGA/My Account/Contacts/VerifyCredentialsViewController.m b/iMEGA/My Account/Contacts/VerifyCredentialsViewController.m index 31562e76c1..1c4ff086b2 100644 --- a/iMEGA/My Account/Contacts/VerifyCredentialsViewController.m +++ b/iMEGA/My Account/Contacts/VerifyCredentialsViewController.m @@ -6,6 +6,8 @@ #import "MEGA-Swift.h" #import "MEGAGenericRequestDelegate.h" +@import MEGAL10nObjc; + @interface VerifyCredentialsViewController () @property (weak, nonatomic) IBOutlet UIView *userCredentialsView; @@ -59,7 +61,7 @@ - (void)viewDidLoad { MEGAGenericRequestDelegate *userCredentialsDelegate = [MEGAGenericRequestDelegate.alloc initWithCompletion:^(MEGARequest *request, MEGAError *error) { if (error.type) { - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; } else { NSString *userCredentials = request.password; if (userCredentials.length == 40) { @@ -78,7 +80,7 @@ - (void)viewDidLoad { }]; [MEGASdkManager.sharedMEGASdk getUserCredentials:self.user delegate:userCredentialsDelegate]; - self.yourCredentialsLabel.text = NSLocalizedString(@"verifyCredentials.yourCredentials.title", @"Title of the label in the my account section. It shows the credentials of the current user so it can be used to be verified by other contacts"); + self.yourCredentialsLabel.text = LocalizedString(@"verifyCredentials.yourCredentials.title", @"Title of the label in the my account section. It shows the credentials of the current user so it can be used to be verified by other contacts"); NSString *yourCredentials = MEGASdkManager.sharedMEGASdk.myCredentials; if (yourCredentials.length == 40) { self.firstPartOfYourCredentialsLabel.text = [yourCredentials substringWithRange:NSMakeRange(0, length)]; @@ -135,10 +137,10 @@ - (void)updateAppearance { - (void)updateVerifyOrResetButton { if ([MEGASdkManager.sharedMEGASdk areCredentialsVerifiedOfUser:self.user]) { - [self.verifyOrResetButton setTitle:NSLocalizedString(@"reset", @"Button to reset the password") forState:UIControlStateNormal]; + [self.verifyOrResetButton setTitle:LocalizedString(@"reset", @"Button to reset the password") forState:UIControlStateNormal]; [self.verifyOrResetButton mnz_setupBasic:self.traitCollection]; } else { - [self.verifyOrResetButton setTitle:NSLocalizedString(@"approve", @"Button title") forState:UIControlStateNormal]; + [self.verifyOrResetButton setTitle:LocalizedString(@"approve", @"Button title") forState:UIControlStateNormal]; [self.verifyOrResetButton mnz_setupPrimary:self.traitCollection]; } } @@ -149,7 +151,7 @@ - (IBAction)verifyOrResetTouchUpInside:(UIButton *)sender { if ([MEGASdkManager.sharedMEGASdk areCredentialsVerifiedOfUser:self.user]) { MEGAGenericRequestDelegate *resetCredentialsOfUserDelegate = [MEGAGenericRequestDelegate.alloc initWithCompletion:^(MEGARequest *request, MEGAError *error) { if (error.type) { - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; } else { [self updateVerifyOrResetButton]; self.statusUpdateCompletionBlock(); @@ -159,9 +161,9 @@ - (IBAction)verifyOrResetTouchUpInside:(UIButton *)sender { } else { MEGAGenericRequestDelegate *verifyCredentialsOfUserDelegate = [MEGAGenericRequestDelegate.alloc initWithCompletion:^(MEGARequest *request, MEGAError *error) { if (error.type) { - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; } else { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"verified", @"Button title")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"verified", @"Button title")]; [self updateVerifyOrResetButton]; self.statusUpdateCompletionBlock(); diff --git a/iMEGA/My Account/Contacts/Views/ContactsTableViewHeader.swift b/iMEGA/My Account/Contacts/Views/ContactsTableViewHeader.swift index e02f604783..0a1e1e4e3c 100644 --- a/iMEGA/My Account/Contacts/Views/ContactsTableViewHeader.swift +++ b/iMEGA/My Account/Contacts/Views/ContactsTableViewHeader.swift @@ -1,5 +1,5 @@ - import Foundation +import MEGAL10n class ContactsTableViewHeader: UIView { @@ -48,9 +48,9 @@ class ContactsTableViewHeader: UIView { } private func configDetailsLabel() { - let incomingContactsLists = MEGASdkManager.sharedMEGASdk().incomingContactRequests() - let contactsCount = incomingContactsLists.size?.intValue ?? 0 - requestsDetailLabel.text = contactsCount == 0 ? "" : incomingContactsLists.size.stringValue + let incomingContactsLists = MEGASdk.shared.incomingContactRequests() + let contactsCount = incomingContactsLists.size + requestsDetailLabel.text = contactsCount == 0 ? "" : String(contactsCount) } // MARK: - IBAction diff --git a/iMEGA/My Account/Launch/Disk Full Blocking Scene/DiskFullBlockingViewModel.swift b/iMEGA/My Account/Launch/Disk Full Blocking Scene/DiskFullBlockingViewModel.swift index 424b7e64c1..6deeb22fc8 100644 --- a/iMEGA/My Account/Launch/Disk Full Blocking Scene/DiskFullBlockingViewModel.swift +++ b/iMEGA/My Account/Launch/Disk Full Blocking Scene/DiskFullBlockingViewModel.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n import MEGAPresentation enum DiskFullBlockingAction: ActionType { diff --git a/iMEGA/My Account/Launch/InitialLaunchViewController.h b/iMEGA/My Account/Launch/InitialLaunchViewController.h index 3504d3139e..c03c282e53 100644 --- a/iMEGA/My Account/Launch/InitialLaunchViewController.h +++ b/iMEGA/My Account/Launch/InitialLaunchViewController.h @@ -1,4 +1,3 @@ - #import "LaunchViewController.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/My Account/Launch/InitialLaunchViewController.m b/iMEGA/My Account/Launch/InitialLaunchViewController.m index 4386a946d1..c08fd266be 100644 --- a/iMEGA/My Account/Launch/InitialLaunchViewController.m +++ b/iMEGA/My Account/Launch/InitialLaunchViewController.m @@ -1,9 +1,10 @@ - #import "InitialLaunchViewController.h" #import "OnboardingViewController.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @interface InitialLaunchViewController () @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @@ -22,10 +23,10 @@ - (void)viewDidLoad { [self updateAppearance]; - self.titleLabel.text = NSLocalizedString(@"Setup MEGA", @"Button which triggers the initial setup"); - self.descriptionLabel.text = NSLocalizedString(@"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:NSLocalizedString(@"Setup MEGA", @"Button which triggers the initial setup") forState:UIControlStateNormal]; - [self.skipButton setTitle:NSLocalizedString(@"skipButton", @"Button title that skips the current action") forState:UIControlStateNormal]; + self.titleLabel.text = LocalizedString(@"Setup MEGA", @"Button which triggers the initial setup"); + self.descriptionLabel.text = LocalizedString(@"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:LocalizedString(@"Setup MEGA", @"Button which triggers the initial setup") forState:UIControlStateNormal]; + [self.skipButton setTitle:LocalizedString(@"skipButton", @"Button title that skips the current action") forState:UIControlStateNormal]; self.setupButton.titleLabel.adjustsFontForContentSizeCategory = YES; self.skipButton.titleLabel.adjustsFontForContentSizeCategory = YES; @@ -81,12 +82,12 @@ - (IBAction)setupButtonPressed:(UIButton *)sender { } - (IBAction)skipButtonPressed:(UIButton *)sender { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"attention", @"Alert title to attract attention") message:NSLocalizedString(@"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:NSLocalizedString(@"yes", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"attention", @"Alert title to attract attention") message:LocalizedString(@"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:LocalizedString(@"yes", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self.delegate setupFinished]; [self.delegate readyToShowRecommendations]; }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"no", nil) style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"no", @"") style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; } diff --git a/iMEGA/My Account/Launch/LaunchViewController.h b/iMEGA/My Account/Launch/LaunchViewController.h index 84ee587b12..64a40f7cd3 100644 --- a/iMEGA/My Account/Launch/LaunchViewController.h +++ b/iMEGA/My Account/Launch/LaunchViewController.h @@ -1,4 +1,3 @@ - #import @protocol LaunchViewControllerDelegate diff --git a/iMEGA/My Account/Launch/LaunchViewController.m b/iMEGA/My Account/Launch/LaunchViewController.m index d981ec5bfb..17746dd52d 100644 --- a/iMEGA/My Account/Launch/LaunchViewController.m +++ b/iMEGA/My Account/Launch/LaunchViewController.m @@ -2,6 +2,7 @@ #import "MEGASdkManager.h" @import MEGASDKRepo; +@import MEGAL10nObjc; @interface LaunchViewController () @@ -80,19 +81,19 @@ - (void)showWaitingReason { NSString *message; switch (MEGASdkManager.sharedMEGASdk.waiting) { case RetryConnectivity: - message = NSLocalizedString(@"unableToReachMega", @"Message shown when the app is waiting for the server to complete a request due to connectivity issue."); + message = LocalizedString(@"unableToReachMega", @"Message shown when the app is waiting for the server to complete a request due to connectivity issue."); break; case RetryServersBusy: - message = NSLocalizedString(@"serversAreTooBusy", @"Message shown when the app is waiting for the server to complete a request due to a HTTP error 500."); + message = LocalizedString(@"serversAreTooBusy", @"Message shown when the app is waiting for the server to complete a request due to a HTTP error 500."); break; case RetryApiLock: - message = NSLocalizedString(@"takingLongerThanExpected", @"Message shown when the app is waiting for the server to complete a request due to an API lock (error -3)."); + message = LocalizedString(@"takingLongerThanExpected", @"Message shown when the app is waiting for the server to complete a request due to an API lock (error -3)."); break; case RetryRateLimit: - message = NSLocalizedString(@"tooManyRequest", @"Message shown when the app is waiting for the server to complete a request due to a rate limit (error -4)."); + message = LocalizedString(@"tooManyRequest", @"Message shown when the app is waiting for the server to complete a request due to a rate limit (error -4)."); break; default: diff --git a/iMEGA/My Account/Login/CheckEmailAndFollowTheLinkViewController.h b/iMEGA/My Account/Login/CheckEmailAndFollowTheLinkViewController.h index acb2cb22a4..b6cf02e393 100644 --- a/iMEGA/My Account/Login/CheckEmailAndFollowTheLinkViewController.h +++ b/iMEGA/My Account/Login/CheckEmailAndFollowTheLinkViewController.h @@ -1,4 +1,3 @@ - #import @interface CheckEmailAndFollowTheLinkViewController : UIViewController diff --git a/iMEGA/My Account/Login/CheckEmailAndFollowTheLinkViewController.m b/iMEGA/My Account/Login/CheckEmailAndFollowTheLinkViewController.m index 6d15c92b5e..3e9412279f 100644 --- a/iMEGA/My Account/Login/CheckEmailAndFollowTheLinkViewController.m +++ b/iMEGA/My Account/Login/CheckEmailAndFollowTheLinkViewController.m @@ -1,4 +1,3 @@ - #import "CheckEmailAndFollowTheLinkViewController.h" #import "SAMKeychain.h" @@ -11,6 +10,8 @@ #import "MEGAReachabilityManager.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @interface CheckEmailAndFollowTheLinkViewController () @property (weak, nonatomic) IBOutlet UIImageView *mailImageView; @@ -40,15 +41,15 @@ - (void)viewDidLoad { self.name = [SAMKeychain passwordForService:@"MEGA" account:@"name"]; self.password = [SAMKeychain passwordForService:@"MEGA" account:@"password"]; - self.awaitingEmailConfirmationLabel.text = NSLocalizedString(@"awaitingEmailConfirmation", @"Title shown just after doing some action that requires confirming the action by an email"); - self.checkYourEmailLabel.text = NSLocalizedString(@"accountNotConfirmed", @"Text shown just after creating an account to remenber the user what to do to complete the account creation proccess"); + self.awaitingEmailConfirmationLabel.text = LocalizedString(@"awaitingEmailConfirmation", @"Title shown just after doing some action that requires confirming the action by an email"); + self.checkYourEmailLabel.text = LocalizedString(@"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 = NSLocalizedString(@"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:NSLocalizedString(@"resend", @"A button to resend the email confirmation.") forState:UIControlStateNormal]; + self.misspelledLabel.text = LocalizedString(@"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:LocalizedString(@"resend", @"A button to resend the email confirmation.") forState:UIControlStateNormal]; - [self.cancelButton setTitle:NSLocalizedString(@"cancel", nil) forState:UIControlStateNormal]; + [self.cancelButton setTitle:LocalizedString(@"cancel", @"") forState:UIControlStateNormal]; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissKeyboard)]; [self.view addGestureRecognizer:tap]; @@ -86,11 +87,11 @@ - (void)dismissKeyboard { - (void)setErrorState:(BOOL)error { if (error) { - self.emailInputView.topLabel.text = NSLocalizedString(@"emailInvalidFormat", @"Message shown when the user writes an invalid format in the email field"); + self.emailInputView.topLabel.text = LocalizedString(@"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 = NSLocalizedString(@"emailPlaceholder", nil); + self.emailInputView.topLabel.text = LocalizedString(@"emailPlaceholder", @""); self.emailInputView.topLabel.textColor = [UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection]; self.emailInputView.inputTextField.textColor = UIColor.mnz_label; } @@ -112,10 +113,10 @@ - (void)updateAppearance { #pragma mark - IBActions - (IBAction)cancelTouchUpInside:(UIButton *)sender { - NSString *message = NSLocalizedString(@"areYouSureYouWantToAbortTheRegistration", @"Asking whether the user really wants to abort/stop the registration process or continue on."); + NSString *message = LocalizedString(@"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:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [MEGASdkManager.sharedMEGASdk cancelCreateAccount]; [Helper clearEphemeralSession]; [self dismissViewControllerAnimated:YES completion:nil]; @@ -136,29 +137,29 @@ - (IBAction)resendTouchUpInside:(UIButton *)sender { switch (error.type) { case MEGAErrorTypeApiEExist: title = @""; - message = NSLocalizedString(@"emailAlreadyRegistered", @"Error text shown when the users tries to create an account with an email already in use"); + message = LocalizedString(@"emailAlreadyRegistered", @"Error text shown when the users tries to create an account with an email already in use"); break; case MEGAErrorTypeApiEFailed: title = @""; - message = NSLocalizedString(@"emailAddressChangeAlreadyRequested", @"Error message shown when you try to change your account email to one that you already requested."); + message = LocalizedString(@"emailAddressChangeAlreadyRequested", @"Error message shown when you try to change your account email to one that you already requested."); break; default: - title = NSLocalizedString(@"error", nil); - message = [NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]; + title = LocalizedString(@"error", @""); + message = [NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]; break; } UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; return; } else { [SAMKeychain setPassword:request.email forService:@"MEGA" account:@"email"]; - [SVProgressHUD showInfoWithStatus:NSLocalizedString(@"awaitingEmailConfirmation", @"Title shown just after doing some action that requires confirming the action by an email")]; + [SVProgressHUD showInfoWithStatus:LocalizedString(@"awaitingEmailConfirmation", @"Title shown just after doing some action that requires confirming the action by an email")]; } }]; [MEGASdkManager.sharedMEGASdk resendSignupLinkWithEmail:self.emailInputView.inputTextField.text diff --git a/iMEGA/My Account/Login/ConfirmAccountViewController.m b/iMEGA/My Account/Login/ConfirmAccountViewController.m index d00c6c32a3..debf928bb3 100644 --- a/iMEGA/My Account/Login/ConfirmAccountViewController.m +++ b/iMEGA/My Account/Login/ConfirmAccountViewController.m @@ -15,6 +15,8 @@ #import "InputView.h" #import "PasswordView.h" +@import MEGAL10nObjc; + @interface ConfirmAccountViewController () @property (weak, nonatomic) IBOutlet UIImageView *logoImageView; @@ -38,30 +40,30 @@ - (void)viewDidLoad { switch (self.urlType) { case URLTypeConfirmationLink: - self.confirmTextLabel.text = NSLocalizedString(@"confirmText", @"Text shown on the confirm account view to remind the user what to do"); - [self.confirmAccountButton setTitle:NSLocalizedString(@"Confirm account", @"Label for any ‘Confirm account’ button, link, text, title, etc. - (String as short as possible).") forState:UIControlStateNormal]; + self.confirmTextLabel.text = LocalizedString(@"confirmText", @"Text shown on the confirm account view to remind the user what to do"); + [self.confirmAccountButton setTitle:LocalizedString(@"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 = NSLocalizedString(@"verifyYourEmailAddress_description", @"Text shown on the confirm email view to remind the user what to do"); - [self.confirmAccountButton setTitle:NSLocalizedString(@"confirmEmail", @"Button text for the user to confirm their change of email address.") forState:UIControlStateNormal]; + self.confirmTextLabel.text = LocalizedString(@"verifyYourEmailAddress_description", @"Text shown on the confirm email view to remind the user what to do"); + [self.confirmAccountButton setTitle:LocalizedString(@"confirmEmail", @"Button text for the user to confirm their change of email address.") forState:UIControlStateNormal]; break; case URLTypeCancelAccountLink: { - NSString* message = NSLocalizedString(@"enterYourPasswordToConfirmThatYouWanToClose", @"Account closure, message shown when you click on the link in the email to confirm the closure of your account"); + NSString* message = LocalizedString(@"enterYourPasswordToConfirmThatYouWanToClose", @"Account closure, message shown when you click on the link in the email to confirm the closure of your account"); MEGAAccountDetails *accountDetails = [[MEGASdkManager sharedMEGASdk] mnz_accountDetails]; if (accountDetails && accountDetails.type != MEGAAccountTypeFree && (accountDetails.subscriptionMethodId == MEGAPaymentMethodECP || accountDetails.subscriptionMethodId == MEGAPaymentMethodStripe2)) { - message = NSLocalizedString(@"account.delete.subscription.webClient", @"Account closure, message shown when you click on the link in the email to confirm the closure of your account"); + message = LocalizedString(@"account.delete.subscription.webClient", @"Account closure, message shown when you click on the link in the email to confirm the closure of your account"); } self.confirmTextLabel.text = message; - [self.confirmAccountButton setTitle:NSLocalizedString(@"cancelYourAccount", @"Account closure, password check dialog when user click on closure email.") forState:UIControlStateNormal]; + [self.confirmAccountButton setTitle:LocalizedString(@"cancelYourAccount", @"Account closure, password check dialog when user click on closure email.") forState:UIControlStateNormal]; [self showSubscriptionDialogIfNeeded]; break; @@ -71,7 +73,7 @@ - (void)viewDidLoad { break; } - [self.cancelButton setTitle:NSLocalizedString(@"cancel", nil) forState:UIControlStateNormal]; + [self.cancelButton setTitle:LocalizedString(@"cancel", @"") forState:UIControlStateNormal]; self.emailInputView.inputTextField.text = self.emailString; self.emailInputView.inputTextField.enabled = NO; @@ -129,12 +131,12 @@ - (IBAction)cancelTouchUpInside:(UIButton *)sender { [self.passwordView.passwordTextField resignFirstResponder]; if (self.urlType == URLTypeConfirmationLink) { - NSString *message = NSLocalizedString(@"areYouSureYouWantToAbortTheRegistration", @"Asking whether the user really wants to abort/stop the registration process or continue on."); + NSString *message = LocalizedString(@"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:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { [MEGALinkManager resetLinkAndURLType]; }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [MEGALinkManager resetLinkAndURLType]; if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionId"]) { @@ -158,7 +160,7 @@ - (BOOL)validateForm { if (validPassword) { [self.passwordView setErrorState:NO]; } else { - [self.passwordView setErrorState:YES withText:NSLocalizedString(@"passwordInvalidFormat", @"Enter a valid password")]; + [self.passwordView setErrorState:YES withText:LocalizedString(@"passwordInvalidFormat", @"Enter a valid password")]; } return validPassword; @@ -267,15 +269,15 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } case MEGAErrorTypeApiEAccess: { - UIAlertController *alreadyLoggedInAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"error", nil) message:NSLocalizedString(@"This link is not related to this account. Please log in with the correct account.", @"Error message shown when opening a link with an account that not corresponds to the link") preferredStyle:UIAlertControllerStyleAlert]; - [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDestructive handler:nil]]; + UIAlertController *alreadyLoggedInAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"error", @"") message:LocalizedString(@"This link is not related to this account. Please log in with the correct account.", @"Error message shown when opening a link with an account that not corresponds to the link") preferredStyle:UIAlertControllerStyleAlert]; + [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDestructive handler:nil]]; [self presentViewController:alreadyLoggedInAlertController animated:YES completion:nil]; break; } case MEGAErrorTypeApiEExist: { - [self.emailInputView setErrorState:YES withText:NSLocalizedString(@"emailAlreadyInUse", @"Error shown when the user tries to change his mail to one that is already used")]; + [self.emailInputView setErrorState:YES withText:LocalizedString(@"emailAlreadyInUse", @"Error shown when the user tries to change his mail to one that is already used")]; break; } @@ -283,7 +285,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG break; default: - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ (%ld)", NSLocalizedString(error.name, nil), (long)error.type]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ (%ld)", LocalizedString(error.name, @""), (long)error.type]]; break; } @@ -318,10 +320,10 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [self.passwordView.passwordTextField resignFirstResponder]; [[NSNotificationCenter defaultCenter] postNotificationName:MEGAEmailHasChangedNotification object:nil]; [self dismissViewControllerAnimated:YES completion:^{ - NSString *alertMessage = [NSLocalizedString(@"congratulationsNewEmailAddress", @"The [X] will be replaced with the e-mail address.") stringByReplacingOccurrencesOfString:@"[X]" withString:request.email]; - UIAlertController *newEmailAddressAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"newEmail", @"Hint text to suggest that the user have to write the new email on it") message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; + NSString *alertMessage = [LocalizedString(@"congratulationsNewEmailAddress", @"The [X] will be replaced with the e-mail address.") stringByReplacingOccurrencesOfString:@"[X]" withString:request.email]; + UIAlertController *newEmailAddressAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"newEmail", @"Hint text to suggest that the user have to write the new email on it") message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - [newEmailAddressAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:nil]]; + [newEmailAddressAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:newEmailAddressAlertController animated:YES completion:nil]; }]; diff --git a/iMEGA/My Account/Login/ConfirmAccountViewcontroller+DeleteAccount.swift b/iMEGA/My Account/Login/ConfirmAccountViewcontroller+DeleteAccount.swift index 978399527a..cc05fac9ec 100644 --- a/iMEGA/My Account/Login/ConfirmAccountViewcontroller+DeleteAccount.swift +++ b/iMEGA/My Account/Login/ConfirmAccountViewcontroller+DeleteAccount.swift @@ -1,6 +1,8 @@ +import MEGAL10n + extension ConfirmAccountViewController { @objc func showSubscriptionDialogIfNeeded() { - guard let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails, + guard let accountDetails = MEGASdk.shared.mnz_accountDetails, accountDetails.type != .free else { return } diff --git a/iMEGA/My Account/Login/CreateAccountViewController+Additions.swift b/iMEGA/My Account/Login/CreateAccountViewController+Additions.swift index e1890d9c4e..1241a91a2c 100644 --- a/iMEGA/My Account/Login/CreateAccountViewController+Additions.swift +++ b/iMEGA/My Account/Login/CreateAccountViewController+Additions.swift @@ -1,3 +1,5 @@ +import MEGAL10n + extension CreateAccountViewController { // MARK: - Login diff --git a/iMEGA/My Account/Login/CreateAccountViewController.m b/iMEGA/My Account/Login/CreateAccountViewController.m index 4471642eb4..1beceb2a4c 100644 --- a/iMEGA/My Account/Login/CreateAccountViewController.m +++ b/iMEGA/My Account/Login/CreateAccountViewController.m @@ -1,4 +1,3 @@ - #import "CreateAccountViewController.h" #import "SVProgressHUD.h" @@ -16,6 +15,8 @@ #import "PasswordStrengthIndicatorView.h" #import "PasswordView.h" +@import MEGAL10nObjc; + typedef NS_ENUM(NSInteger, TextFieldTag) { FirstNameTextFieldTag = 0, LastNameTextFieldTag, @@ -73,7 +74,7 @@ - (void)viewDidLoad { [self.loginLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapLogin)]]; - self.cancelBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.cancelBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); self.firstNameInputView.inputTextField.returnKeyType = UIReturnKeyNext; self.firstNameInputView.inputTextField.delegate = self; @@ -105,7 +106,7 @@ - (void)viewDidLoad { self.passwordView.passwordTextField.textContentType = UITextContentTypeNewPassword; self.retypePasswordView.passwordTextField.textContentType = UITextContentTypePassword; - [self.createAccountButton setTitle:NSLocalizedString(@"createAccount", @"Button title which triggers the action to create a MEGA account") forState:UIControlStateNormal]; + [self.createAccountButton setTitle:LocalizedString(@"createAccount", @"Button title which triggers the action to create a MEGA account") forState:UIControlStateNormal]; [self registerForKeyboardNotifications]; } @@ -113,7 +114,7 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [self.navigationController.navigationBar.topItem setTitle:NSLocalizedString(@"createAccount", nil)]; + [self.navigationController.navigationBar.topItem setTitle:LocalizedString(@"createAccount", @"")]; } - (void)viewWillDisappear:(BOOL)animated { @@ -184,7 +185,7 @@ - (BOOL)validateForm { if (!self.termsCheckboxButton.isSelected) { if (valid) { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudWarning"] status:NSLocalizedString(@"termsCheckboxUnselected", @"Error text shown when you don't have selected the checkbox to agree with the Terms of Service")]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudWarning"] status:LocalizedString(@"termsCheckboxUnselected", @"Error text shown when you don't have selected the checkbox to agree with the Terms of Service")]; } valid = NO; @@ -193,7 +194,7 @@ - (BOOL)validateForm { if (!self.termsForLosingPasswordCheckboxButton.isSelected) { if (valid) { [SVProgressHUD showImage:[UIImage imageNamed:@"hudWarning"] - status:NSLocalizedString(@"termsForLosingPasswordCheckboxUnselected", nil)]; + status:LocalizedString(@"termsForLosingPasswordCheckboxUnselected", @"")]; } valid = NO; @@ -205,10 +206,10 @@ - (BOOL)validateForm { - (BOOL)validateFirstName { self.firstNameInputView.inputTextField.text = self.firstNameInputView.inputTextField.text.mnz_removeWhitespacesAndNewlinesFromBothEnds; if (self.firstNameInputView.inputTextField.text.mnz_isEmpty) { - [self.firstNameInputView setErrorState:YES withText:NSLocalizedString(@"nameInvalidFormat", @"Error text shown when you have not entered a correct name")]; + [self.firstNameInputView setErrorState:YES withText:LocalizedString(@"nameInvalidFormat", @"Error text shown when you have not entered a correct name")]; return NO; } else { - [self.firstNameInputView setErrorState:NO withText:NSLocalizedString(@"firstName", @"Hint text for the first name (Placeholder)")]; + [self.firstNameInputView setErrorState:NO withText:LocalizedString(@"firstName", @"Hint text for the first name (Placeholder)")]; return YES; } } @@ -216,10 +217,10 @@ - (BOOL)validateFirstName { - (BOOL)validateLastName { self.lastNameInputView.inputTextField.text = self.lastNameInputView.inputTextField.text.mnz_removeWhitespacesAndNewlinesFromBothEnds; if (self.lastNameInputView.inputTextField.text.mnz_isEmpty) { - [self.lastNameInputView setErrorState:YES withText:NSLocalizedString(@"lastName", @"Hint text for the last name (Placeholder)")]; + [self.lastNameInputView setErrorState:YES withText:LocalizedString(@"lastName", @"Hint text for the last name (Placeholder)")]; return NO; } else { - [self.lastNameInputView setErrorState:NO withText:NSLocalizedString(@"lastName", @"Hint text for the last name (Placeholder)")]; + [self.lastNameInputView setErrorState:NO withText:LocalizedString(@"lastName", @"Hint text for the last name (Placeholder)")]; return YES; } } @@ -227,33 +228,33 @@ - (BOOL)validateLastName { - (BOOL)validateEmail { self.emailInputView.inputTextField.text = self.emailInputView.inputTextField.text.mnz_removeWhitespacesAndNewlinesFromBothEnds; if (self.emailInputView.inputTextField.text.mnz_isValidEmail) { - [self.emailInputView setErrorState:NO withText:NSLocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; + [self.emailInputView setErrorState:NO withText:LocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; return YES; } else { - [self.emailInputView setErrorState:YES withText:NSLocalizedString(@"emailInvalidFormat", @"Message shown when the user writes an invalid format in the email field")]; + [self.emailInputView setErrorState:YES withText:LocalizedString(@"emailInvalidFormat", @"Message shown when the user writes an invalid format in the email field")]; return NO; } } - (BOOL)validatePassword { if (self.passwordView.passwordTextField.text.mnz_isEmpty) { - [self.passwordView setErrorState:YES withText:NSLocalizedString(@"passwordInvalidFormat", @"Message shown when the user enters a wrong password")]; + [self.passwordView setErrorState:YES withText:LocalizedString(@"passwordInvalidFormat", @"Message shown when the user enters a wrong password")]; return NO; } else if ([[MEGASdkManager sharedMEGASdk] passwordStrength:self.passwordView.passwordTextField.text] == PasswordStrengthVeryWeak) { - [self.passwordView setErrorState:YES withText:NSLocalizedString(@"pleaseStrengthenYourPassword", nil)]; + [self.passwordView setErrorState:YES withText:LocalizedString(@"pleaseStrengthenYourPassword", @"")]; return NO; } else { - [self.passwordView setErrorState:NO withText:NSLocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password")]; + [self.passwordView setErrorState:NO withText:LocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password")]; return YES; } } - (BOOL)validateRetypePassword { if ([self.retypePasswordView.passwordTextField.text isEqualToString:self.passwordView.passwordTextField.text]) { - [self.retypePasswordView setErrorState:NO withText:NSLocalizedString(@"confirmPassword", @"Hint text where the user have to re-write the new password to confirm it")]; + [self.retypePasswordView setErrorState:NO withText:LocalizedString(@"confirmPassword", @"Hint text where the user have to re-write the new password to confirm it")]; return YES; } else { - [self.retypePasswordView setErrorState:YES withText:NSLocalizedString(@"passwordsDoNotMatch", @"Error text shown when you have not written the same password")]; + [self.retypePasswordView setErrorState:YES withText:LocalizedString(@"passwordsDoNotMatch", @"Error text shown when you have not written the same password")]; return NO; } } @@ -295,7 +296,7 @@ - (void)keyboardWillBeHidden:(NSNotification *)aNotification { } - (void)setTermsOfServiceAttributedText { - NSString *agreeWithTheMEGATermsOfService = NSLocalizedString(@"agreeWithTheMEGATermsOfService", @""); + NSString *agreeWithTheMEGATermsOfService = LocalizedString(@"agreeWithTheMEGATermsOfService", @""); NSString *termsOfServiceString = [agreeWithTheMEGATermsOfService mnz_stringBetweenString:@"" andString:@""]; if (!termsOfServiceString) { termsOfServiceString = [agreeWithTheMEGATermsOfService mnz_stringBetweenString:@"" andString:@""]; @@ -313,7 +314,7 @@ - (void)setTermsOfServiceAttributedText { } - (void)setTermsForLosingPasswordAttributedText { - NSString *agreementForLosingPasswordText = NSLocalizedString(@"agreeWithLosingPasswordYouLoseData", @""); + NSString *agreementForLosingPasswordText = LocalizedString(@"agreeWithLosingPasswordYouLoseData", @""); NSString *semiboldPrimaryGrayText = [agreementForLosingPasswordText mnz_stringBetweenString:@"[S]" andString:@"[/S]"]; NSString *greenText = [agreementForLosingPasswordText mnz_stringBetweenString:@"[/S]" andString:@""]; @@ -412,7 +413,7 @@ - (IBAction)createAccountTouchUpInside:(id)sender { [UIApplication.mnz_presentingViewController presentViewController:checkEmailAndFollowTheLinkVC animated:YES completion:nil]; }]; } else { - [self.emailInputView setErrorState:YES withText:NSLocalizedString(@"emailAlreadyInUse", @"Error shown when the user tries to change his mail to one that is already used")]; + [self.emailInputView setErrorState:YES withText:LocalizedString(@"emailAlreadyInUse", @"Error shown when the user tries to change his mail to one that is already used")]; [self.emailInputView.inputTextField becomeFirstResponder]; } }]; @@ -494,23 +495,23 @@ - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRang switch (textField.tag) { case FirstNameTextFieldTag: - [self.firstNameInputView setErrorState:NO withText:NSLocalizedString(@"firstName", @"Hint text for the first name (Placeholder)")]; + [self.firstNameInputView setErrorState:NO withText:LocalizedString(@"firstName", @"Hint text for the first name (Placeholder)")]; break; case LastNameTextFieldTag: - [self.lastNameInputView setErrorState:NO withText:NSLocalizedString(@"lastName", @"Hint text for the last name (Placeholder)")]; + [self.lastNameInputView setErrorState:NO withText:LocalizedString(@"lastName", @"Hint text for the last name (Placeholder)")]; break; case EmailTextFieldTag: - [self.emailInputView setErrorState:NO withText:NSLocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; + [self.emailInputView setErrorState:NO withText:LocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; break; case PasswordTextFieldTag: - [self.passwordView setErrorState:NO withText:NSLocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password")]; + [self.passwordView setErrorState:NO withText:LocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password")]; break; case RetypeTextFieldTag: - [self.retypePasswordView setErrorState:NO withText:NSLocalizedString(@"confirmPassword", @"Hint text where the user have to re-write the new password to confirm it")]; + [self.retypePasswordView setErrorState:NO withText:LocalizedString(@"confirmPassword", @"Hint text where the user have to re-write the new password to confirm it")]; break; default: diff --git a/iMEGA/My Account/Login/LoginViewController.h b/iMEGA/My Account/Login/LoginViewController.h index b00f6ce338..31330d81c5 100644 --- a/iMEGA/My Account/Login/LoginViewController.h +++ b/iMEGA/My Account/Login/LoginViewController.h @@ -1,4 +1,3 @@ - #import @interface LoginViewController : UIViewController diff --git a/iMEGA/My Account/Login/LoginViewController.m b/iMEGA/My Account/Login/LoginViewController.m index 3df656e819..1f55bf1cbe 100644 --- a/iMEGA/My Account/Login/LoginViewController.m +++ b/iMEGA/My Account/Login/LoginViewController.m @@ -12,6 +12,8 @@ #import "TwoFactorAuthenticationViewController.h" #import "PasswordView.h" +@import MEGAL10nObjc; + typedef NS_ENUM(NSInteger, TextFieldTag) { EmailTextFieldTag = 0, PasswordTextFieldTag @@ -60,7 +62,7 @@ - (void)viewDidLoad { longPressGestureRecognizer.minimumPressDuration = 5.0f; self.logoImageView.gestureRecognizers = @[tapGestureRecognizer, longPressGestureRecognizer]; - self.cancelBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.cancelBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); self.emailInputView.inputTextField.returnKeyType = UIReturnKeyNext; self.emailInputView.inputTextField.delegate = self; @@ -72,9 +74,9 @@ - (void)viewDidLoad { self.passwordView.passwordTextField.tag = PasswordTextFieldTag; self.passwordView.passwordTextField.textContentType = UITextContentTypePassword; - [self.loginButton setTitle:NSLocalizedString(@"login", @"Login") forState:UIControlStateNormal]; + [self.loginButton setTitle:LocalizedString(@"login", @"Login") forState:UIControlStateNormal]; - NSString *forgotPasswordString = NSLocalizedString(@"forgotPassword", @"An option to reset the password."); + NSString *forgotPasswordString = LocalizedString(@"forgotPassword", @"An option to reset the password."); forgotPasswordString = [forgotPasswordString stringByReplacingOccurrencesOfString:@"?" withString:@""]; forgotPasswordString = [forgotPasswordString stringByReplacingOccurrencesOfString:@"¿" withString:@""]; [self.forgotPasswordButton setTitle:forgotPasswordString forState:UIControlStateNormal]; @@ -85,7 +87,7 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [self.navigationItem setTitle:NSLocalizedString(@"login", nil)]; + [self.navigationItem setTitle:LocalizedString(@"login", @"")]; if (self.emailString) { self.emailInputView.inputTextField.text = self.emailString; @@ -202,9 +204,9 @@ - (BOOL)validateEmail { self.emailInputView.inputTextField.text = self.emailInputView.inputTextField.text.mnz_removeWhitespacesAndNewlinesFromBothEnds; BOOL validEmail = self.emailInputView.inputTextField.text.mnz_isValidEmail; if (validEmail) { - [self.emailInputView setErrorState:NO withText:NSLocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; + [self.emailInputView setErrorState:NO withText:LocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; } else { - [self.emailInputView setErrorState:YES withText:NSLocalizedString(@"emailInvalidFormat", @"Enter a valid email")]; + [self.emailInputView setErrorState:YES withText:LocalizedString(@"emailInvalidFormat", @"Enter a valid email")]; } return validEmail; @@ -216,7 +218,7 @@ - (BOOL)validatePassword { if (validPassword) { [self.passwordView setErrorState:NO]; } else { - [self.passwordView setErrorState:YES withText:NSLocalizedString(@"passwordInvalidFormat", @"Enter a valid password")]; + [self.passwordView setErrorState:YES withText:LocalizedString(@"passwordInvalidFormat", @"Enter a valid password")]; } return validPassword; @@ -314,7 +316,7 @@ - (void)textFieldDidEndEditing:(UITextField *)textField { - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { switch (textField.tag) { case EmailTextFieldTag: - [self.emailInputView setErrorState:NO withText:NSLocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; + [self.emailInputView setErrorState:NO withText:LocalizedString(@"emailPlaceholder", @"Hint text to suggest that the user has to write his email")]; break; case PasswordTextFieldTag: @@ -352,8 +354,8 @@ - (BOOL)textFieldShouldReturn:(UITextField *)textField { #pragma mark - Create Account - (void)setCreateAccountAttributedText { - NSString *newToMegaString = NSLocalizedString(@"account.login.newToMega", "New to MEGA?"); - NSString *createAccountString = NSLocalizedString(@"createAccount", "Create Account"); + NSString *newToMegaString = LocalizedString(@"account.login.newToMega", @"New to MEGA?"); + NSString *createAccountString = LocalizedString(@"createAccount", @"Create Account"); UIFont *font = [UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightRegular]; NSAttributedString *newToMegaAttributedString = [NSAttributedString.alloc diff --git a/iMEGA/Utils/Categories/MainTabBarController+Additions.swift b/iMEGA/My Account/Login/MainTabBarController+Additions.swift similarity index 66% rename from iMEGA/Utils/Categories/MainTabBarController+Additions.swift rename to iMEGA/My Account/Login/MainTabBarController+Additions.swift index b298418f35..5c177589cd 100644 --- a/iMEGA/Utils/Categories/MainTabBarController+Additions.swift +++ b/iMEGA/My Account/Login/MainTabBarController+Additions.swift @@ -1,8 +1,20 @@ import MEGADomain +import MEGAPresentation import MEGASDKRepo extension MainTabBarController { + private var shouldUseNewHomeSearchResults: Bool { + DIContainer.featureFlagProvider.isFeatureFlagEnabled(for: .newHomeSearch) + } + + @objc func makeHomeViewController() -> UIViewController { + HomeScreenFactory().createHomeScreen( + from: self, + newHomeSearchResultsEnabled: shouldUseNewHomeSearchResults + ) + } + @objc func createPSAViewModel() -> PSAViewModel? { let router = PSAViewRouter(tabBarController: self) let useCase = PSAUseCase(repo: PSARepository.newRepo) diff --git a/iMEGA/My Account/Login/MainTabBarController+CameraUpload.h b/iMEGA/My Account/Login/MainTabBarController+CameraUpload.h index 42fb70e2ea..1b7fbb8fdc 100644 --- a/iMEGA/My Account/Login/MainTabBarController+CameraUpload.h +++ b/iMEGA/My Account/Login/MainTabBarController+CameraUpload.h @@ -1,4 +1,3 @@ - #import "MainTabBarController.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/My Account/Login/MainTabBarController+CameraUpload.m b/iMEGA/My Account/Login/MainTabBarController+CameraUpload.m index 3b35ad9497..4b939e1233 100644 --- a/iMEGA/My Account/Login/MainTabBarController+CameraUpload.m +++ b/iMEGA/My Account/Login/MainTabBarController+CameraUpload.m @@ -1,4 +1,3 @@ - #import "MainTabBarController+CameraUpload.h" #import "CameraUploadManager+Settings.h" #import "CustomModalAlertViewController.h" @@ -6,6 +5,8 @@ #import "UIApplication+MNZCategory.h" #import "MEGANavigationController.h" +@import MEGAL10nObjc; + @implementation MainTabBarController (CameraUpload) #pragma mark - Camera Upload v2 migration @@ -17,10 +18,10 @@ - (void)showCameraUploadV2MigrationScreenIfNeeded { CustomModalAlertViewController *migrationVC = [[CustomModalAlertViewController alloc] init]; migrationVC.image = [UIImage imageNamed:@"cameraUploadsV2Migration"]; - migrationVC.viewTitle = NSLocalizedString(@"New Camera Upload!", nil); - migrationVC.detail = NSLocalizedString(@"Now you can choose to convert the HEIF/HEVC photos and videos to the most compatible JPEG/H.264 formats.", nil); - migrationVC.firstButtonTitle = NSLocalizedString(@"Use Most Compatible Formats", nil); - migrationVC.dismissButtonTitle = NSLocalizedString(@"Custom Settings", nil); + migrationVC.viewTitle = LocalizedString(@"New Camera Upload!", @""); + migrationVC.detail = LocalizedString(@"Now you can choose to convert the HEIF/HEVC photos and videos to the most compatible JPEG/H.264 formats.", @""); + migrationVC.firstButtonTitle = LocalizedString(@"Use Most Compatible Formats", @""); + migrationVC.dismissButtonTitle = LocalizedString(@"Custom Settings", @""); __weak __typeof__(CustomModalAlertViewController) *weakCustom = migrationVC; migrationVC.firstCompletion = ^{ diff --git a/iMEGA/My Account/Login/MainTabBarController+SnackBar.swift b/iMEGA/My Account/Login/MainTabBarController+SnackBar.swift index a6551071ae..59b36f9767 100644 --- a/iMEGA/My Account/Login/MainTabBarController+SnackBar.swift +++ b/iMEGA/My Account/Login/MainTabBarController+SnackBar.swift @@ -1,4 +1,3 @@ - extension MainTabBarController: SnackBarPresenting { @MainActor func layout(snackBarView: UIView?) { diff --git a/iMEGA/My Account/Login/MainTabBarController.m b/iMEGA/My Account/Login/MainTabBarController.m index b6856607ee..95ef3cc0f0 100644 --- a/iMEGA/My Account/Login/MainTabBarController.m +++ b/iMEGA/My Account/Login/MainTabBarController.m @@ -1,4 +1,3 @@ - #import "MainTabBarController.h" #import "CloudDriveViewController.h" @@ -12,6 +11,8 @@ #import "MEGA-Swift.h" #import "NSObject+Debounce.h" + +@import MEGAL10nObjc; @import PureLayout; @interface MainTabBarController () @@ -34,7 +35,7 @@ - (void)viewDidLoad { [defaultViewControllersMutableArray addObject:[self cloudDriveViewController]]; [defaultViewControllersMutableArray addObject:[self photosViewController]]; - [defaultViewControllersMutableArray addObject:[self homeViewController]]; + [defaultViewControllersMutableArray addObject:[self makeHomeViewController]]; [defaultViewControllersMutableArray addObject:[self chatViewController]]; [defaultViewControllersMutableArray addObject:[self SharedItemsViewController]]; @@ -190,12 +191,11 @@ - (void)showScanDocument { - (void)showAddContact { InviteContactViewController *inviteContactVC = [[UIStoryboard storyboardWithName:@"InviteContact" bundle:nil] instantiateViewControllerWithIdentifier:@"InviteContactViewControllerID"]; MEGANavigationController *navigation = [MEGANavigationController.alloc initWithRootViewController:inviteContactVC]; - [navigation addLeftDismissButtonWithText:NSLocalizedString(@"close", @"A button label. The button allows the user to close the conversation.")]; + [navigation addLeftDismissButtonWithText:LocalizedString(@"close", @"A button label. The button allows the user to close the conversation.")]; [self presentViewController:navigation animated:YES completion:nil]; } - (void)configProgressView { - [TransfersWidgetViewController.sharedTransferViewController configProgressIndicator]; [TransfersWidgetViewController.sharedTransferViewController setProgressViewInKeyWindow]; } @@ -274,10 +274,6 @@ - (UIViewController *)photosViewController { return [self photoAlbumViewController]; } -- (UIViewController *)homeViewController { - return [HomeScreenFactory.new createHomeScreenFrom:self]; -} - - (UIViewController *)SharedItemsViewController { MEGANavigationController *sharedItemsNavigationController = [[UIStoryboard storyboardWithName:@"SharedItems" bundle:nil] instantiateInitialViewController]; if ([[sharedItemsNavigationController.viewControllers firstObject] conformsToProtocol:@protocol(MyAvatarPresenterProtocol)]) { diff --git a/iMEGA/My Account/Notifications/NotificationTableViewCell.h b/iMEGA/My Account/Notifications/NotificationTableViewCell.h index 9c605d7a14..dcbd21d049 100644 --- a/iMEGA/My Account/Notifications/NotificationTableViewCell.h +++ b/iMEGA/My Account/Notifications/NotificationTableViewCell.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/My Account/Notifications/NotificationTableViewCell.m b/iMEGA/My Account/Notifications/NotificationTableViewCell.m index edcf729460..f25b7eccb2 100644 --- a/iMEGA/My Account/Notifications/NotificationTableViewCell.m +++ b/iMEGA/My Account/Notifications/NotificationTableViewCell.m @@ -1,6 +1,7 @@ - #import "NotificationTableViewCell.h" +@import MEGAL10nObjc; + @implementation NotificationTableViewCell - (void)awakeFromNib { @@ -9,7 +10,7 @@ - (void)awakeFromNib { self.theNewView.backgroundColor = [UIColor mnz_turquoiseForTraitCollection:self.traitCollection]; self.theNewLabel.textColor = UIColor.whiteColor; - self.theNewLabel.text = NSLocalizedString(@"New", @"Label shown inside an unseen notification"); + self.theNewLabel.text = LocalizedString(@"New", @"Label shown inside an unseen notification"); } - (void)prepareForReuse { diff --git a/iMEGA/My Account/Notifications/NotificationsTableViewController+Additions.swift b/iMEGA/My Account/Notifications/NotificationsTableViewController+Additions.swift index e7669866a0..0739faaf6b 100644 --- a/iMEGA/My Account/Notifications/NotificationsTableViewController+Additions.swift +++ b/iMEGA/My Account/Notifications/NotificationsTableViewController+Additions.swift @@ -1,9 +1,10 @@ import MEGADomain +import MEGAL10n extension NotificationsTableViewController { @objc func contentForTakedownReinstatedNode(withHandle handle: HandleEntity, nodeFont: UIFont) -> NSAttributedString? { - guard let node = MEGASdkManager.sharedMEGASdk().node(forHandle: handle) else { return nil } + guard let node = MEGASdk.shared.node(forHandle: handle) else { return nil } let nodeName = node.name ?? "" switch node.type { case .file: @@ -19,7 +20,7 @@ extension NotificationsTableViewController { } @objc func contentForTakedownPubliclySharedNode(withHandle handle: HandleEntity, nodeFont: UIFont) -> NSAttributedString? { - guard let node = MEGASdkManager.sharedMEGASdk().node(forHandle: handle) else { return nil } + guard let node = MEGASdk.shared.node(forHandle: handle) else { return nil } let nodeName = node.name ?? "" switch node.type { case .file: diff --git a/iMEGA/My Account/Notifications/NotificationsTableViewController+Meetings.swift b/iMEGA/My Account/Notifications/NotificationsTableViewController+Meetings.swift index 525f4ce064..822f3d5eae 100644 --- a/iMEGA/My Account/Notifications/NotificationsTableViewController+Meetings.swift +++ b/iMEGA/My Account/Notifications/NotificationsTableViewController+Meetings.swift @@ -1,5 +1,6 @@ import MEGADomain import MEGAFoundation +import MEGAL10n extension NotificationsTableViewController { diff --git a/iMEGA/My Account/Notifications/NotificationsTableViewController.h b/iMEGA/My Account/Notifications/NotificationsTableViewController.h index 51e327016b..3621d1d89c 100644 --- a/iMEGA/My Account/Notifications/NotificationsTableViewController.h +++ b/iMEGA/My Account/Notifications/NotificationsTableViewController.h @@ -1,4 +1,3 @@ - #import @class ScheduleMeetingOccurrenceNotification; diff --git a/iMEGA/My Account/Notifications/NotificationsTableViewController.m b/iMEGA/My Account/Notifications/NotificationsTableViewController.m index a9d5d27fd4..4f8275409d 100644 --- a/iMEGA/My Account/Notifications/NotificationsTableViewController.m +++ b/iMEGA/My Account/Notifications/NotificationsTableViewController.m @@ -1,4 +1,3 @@ - #import "NotificationsTableViewController.h" #import "UIScrollView+EmptyDataSet.h" @@ -19,6 +18,7 @@ #import "NotificationTableViewCell.h" #import "SharedItemsViewController.h" +@import MEGAL10nObjc; @import MEGASDKRepo; @interface NotificationsTableViewController () @@ -36,7 +36,7 @@ - (void)viewDidLoad { self.tableView.emptyDataSetDelegate = self; self.tableView.emptyDataSetSource = self; - self.navigationItem.title = NSLocalizedString(@"notifications", nil); + self.navigationItem.title = LocalizedString(@"notifications", @""); [self fetchAlerts]; [self logUserAlertsStatus:self.userAlertsArray]; @@ -96,7 +96,7 @@ - (void)configureTypeLabel:(UILabel *)typeLabel forType:(MEGAUserAlertType)type case MEGAUserAlertTypeUpdatePendingContactIncomingDenied: case MEGAUserAlertTypeUpdatePendingContactOutgoingAccepted: case MEGAUserAlertTypeUpdatePendingContactOutgoingDenied: - typeLabel.text = NSLocalizedString(@"contactsTitle", @"Title of the Contacts section"); + typeLabel.text = LocalizedString(@"contactsTitle", @"Title of the Contacts section"); typeLabel.textColor = [UIColor mnz_turquoiseForTraitCollection:self.traitCollection]; break; @@ -104,35 +104,35 @@ - (void)configureTypeLabel:(UILabel *)typeLabel forType:(MEGAUserAlertType)type case MEGAUserAlertTypeDeletedShare: case MEGAUserAlertTypeNewShareNodes: case MEGAUserAlertTypeRemovedSharesNodes: - typeLabel.text = NSLocalizedString(@"shared", @"Title of the tab bar item for the Shared Items section"); + typeLabel.text = LocalizedString(@"shared", @"Title of the tab bar item for the Shared Items section"); typeLabel.textColor = UIColor.systemOrangeColor; break; case MEGAUserAlertTypePaymentSucceeded: case MEGAUserAlertTypePaymentFailed: - typeLabel.text = NSLocalizedString(@"Payment info", @"The header of a notification related to payments"); + typeLabel.text = LocalizedString(@"Payment info", @"The header of a notification related to payments"); typeLabel.textColor = [UIColor mnz_redForTraitCollection:(self.traitCollection)]; break; case MEGAUserAlertTypePaymentReminder: - typeLabel.text = NSLocalizedString(@"PRO membership plan expiring soon", @"A title for a notification saying the user’s pricing plan will expire soon."); + typeLabel.text = LocalizedString(@"PRO membership plan expiring soon", @"A title for a notification saying the user’s pricing plan will expire soon."); typeLabel.textColor = [UIColor mnz_redForTraitCollection:(self.traitCollection)]; break; case MEGAUserAlertTypeTakedown: - typeLabel.text = NSLocalizedString(@"Takedown notice", @"The header of a notification indicating that a file or folder has been taken down due to infringement or other reason."); + typeLabel.text = LocalizedString(@"Takedown notice", @"The header of a notification indicating that a file or folder has been taken down due to infringement or other reason."); typeLabel.textColor = [UIColor mnz_redForTraitCollection:(self.traitCollection)]; break; case MEGAUserAlertTypeTakedownReinstated: - typeLabel.text = NSLocalizedString(@"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."); + typeLabel.text = LocalizedString(@"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."); typeLabel.textColor = [UIColor mnz_redForTraitCollection:(self.traitCollection)]; break; case MEGAUserAlertTypeScheduledMeetingNew: case MEGAUserAlertTypeScheduledMeetingUpdated: case MEGAUserAlertTypeScheduledMeetingDeleted: - typeLabel.text = NSLocalizedString(@"inapp.notifications.meetings.header", @"The header of a notification that is related to scheduled meetings"); + typeLabel.text = LocalizedString(@"inapp.notifications.meetings.header", @"The header of a notification that is related to scheduled meetings"); typeLabel.textColor = [UIColor mnz_redForTraitCollection:(self.traitCollection)]; break; @@ -204,68 +204,68 @@ - (void)configureContentLabel:(UILabel *)contentLabel forAlert:(MEGAUserAlert *) switch (userAlert.type) { case MEGAUserAlertTypeIncomingPendingContactRequest: - contentLabel.text = NSLocalizedString(@"Sent you a contact request", @"When a contact sent a contact/friend request"); + contentLabel.text = LocalizedString(@"Sent you a contact request", @"When a contact sent a contact/friend request"); break; case MEGAUserAlertTypeIncomingPendingContactCancelled: - contentLabel.text = NSLocalizedString(@"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."); + contentLabel.text = LocalizedString(@"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 = NSLocalizedString(@"Reminder: You have a contact request", @"A reminder notification to remind the user to respond to the contact request."); + contentLabel.text = LocalizedString(@"Reminder: You have a contact request", @"A reminder notification to remind the user to respond to the contact request."); break; case MEGAUserAlertTypeContactChangeDeletedYou: - contentLabel.text = NSLocalizedString(@"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."); + contentLabel.text = LocalizedString(@"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 = NSLocalizedString(@"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)."); + contentLabel.text = LocalizedString(@"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 = NSLocalizedString(@"Account has been deleted/deactivated", @"A notification telling the user that one of their contact’s accounts has been deleted or deactivated."); + contentLabel.text = LocalizedString(@"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 = NSLocalizedString(@"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."); + contentLabel.text = LocalizedString(@"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 = NSLocalizedString(@"You ignored a contact request", @"Response text after clicking Ignore on an incoming contact request notification."); + contentLabel.text = LocalizedString(@"You ignored a contact request", @"Response text after clicking Ignore on an incoming contact request notification."); break; case MEGAUserAlertTypeUpdatePendingContactIncomingAccepted: - contentLabel.text = NSLocalizedString(@"You accepted a contact request", @"Response text after clicking Accept on an incoming contact request notification."); + contentLabel.text = LocalizedString(@"You accepted a contact request", @"Response text after clicking Accept on an incoming contact request notification."); break; case MEGAUserAlertTypeUpdatePendingContactIncomingDenied: - contentLabel.text = NSLocalizedString(@"You denied a contact request", @"Response text after clicking Deny on an incoming contact request notification."); + contentLabel.text = LocalizedString(@"You denied a contact request", @"Response text after clicking Deny on an incoming contact request notification."); break; case MEGAUserAlertTypeUpdatePendingContactOutgoingAccepted: - contentLabel.text = NSLocalizedString(@"Accepted your contact request", @"When somebody accepted your contact request"); + contentLabel.text = LocalizedString(@"Accepted your contact request", @"When somebody accepted your contact request"); break; case MEGAUserAlertTypeUpdatePendingContactOutgoingDenied: - contentLabel.text = NSLocalizedString(@"Denied your contact request", @"When somebody denied your contact request"); + contentLabel.text = LocalizedString(@"Denied your contact request", @"When somebody denied your contact request"); break; case MEGAUserAlertTypeNewShare: - contentLabel.text = NSLocalizedString(@"newSharedFolder", @"Notification text body shown when you have received a new shared folder"); + contentLabel.text = LocalizedString(@"newSharedFolder", @"Notification text body shown when you have received a new shared folder"); break; case MEGAUserAlertTypeDeletedShare: { MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForHandle:userAlert.nodeHandle]; if ([userAlert numberAtIndex:0] == 0) { NSAttributedString *nodeName = [[NSAttributedString alloc] initWithString:node.name ?: @"" attributes:@{ NSFontAttributeName : boldFont }]; - NSString *text = NSLocalizedString(@"A user has left the shared folder {0}", @"notification text"); + NSString *text = LocalizedString(@"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 = NSLocalizedString(@"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)."); + contentLabel.text = LocalizedString(@"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; } @@ -275,28 +275,28 @@ - (void)configureContentLabel:(UILabel *)contentLabel forAlert:(MEGAUserAlert *) int64_t folderCount = [userAlert numberAtIndex:0]; NSString *text; if ((folderCount > 1) && (fileCount > 1)) { - text = [[NSLocalizedString(@"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]]; + text = [[LocalizedString(@"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:NSLocalizedString(@"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]; + text = [NSString stringWithFormat:LocalizedString(@"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:NSLocalizedString(@"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]; + text = [NSString stringWithFormat:LocalizedString(@"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 = NSLocalizedString(@"Added 1 file and 1 folder", @"Content of a notification that informs how many files and folders have been added to a shared folder"); + text = LocalizedString(@"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:NSLocalizedString(@"Added %lld folders", @"Content of a notification that informs how many files and folders have been added to a shared folder"), folderCount]; + text = [NSString stringWithFormat:LocalizedString(@"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:NSLocalizedString(@"Added %lld files", @"Content of a notification that informs how many files and folders have been added to a shared folder"), fileCount]; + text = [NSString stringWithFormat:LocalizedString(@"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 = NSLocalizedString(@"Added 1 folder", @"Content of a notification that informs how many files and folders have been added to a shared folder"); + text = LocalizedString(@"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 = NSLocalizedString(@"Added 1 file", @"Content of a notification that informs how many files and folders have been added to a shared folder"); + text = LocalizedString(@"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; } @@ -307,9 +307,9 @@ - (void)configureContentLabel:(UILabel *)contentLabel forAlert:(MEGAUserAlert *) case MEGAUserAlertTypeRemovedSharesNodes: { int64_t itemCount = [userAlert numberAtIndex:0]; if (itemCount == 1) { - contentLabel.text = NSLocalizedString(@"Removed item from shared folder", @"Notification when on client side when owner of a shared folder removes folder/file from it."); + contentLabel.text = LocalizedString(@"Removed item from shared folder", @"Notification when on client side when owner of a shared folder removes folder/file from it."); } else { - contentLabel.text = [NSLocalizedString(@"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]]; + contentLabel.text = [LocalizedString(@"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; } @@ -317,7 +317,7 @@ - (void)configureContentLabel:(UILabel *)contentLabel forAlert:(MEGAUserAlert *) case MEGAUserAlertTypePaymentSucceeded: { NSString *proPlanString = [userAlert stringAtIndex:0] ? [userAlert stringAtIndex:0] : @""; NSAttributedString *proPlan = [[NSAttributedString alloc] initWithString:proPlanString attributes:@{ NSFontAttributeName : boldFont }]; - NSString *text = NSLocalizedString(@"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."); + NSString *text = LocalizedString(@"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]; @@ -328,7 +328,7 @@ - (void)configureContentLabel:(UILabel *)contentLabel forAlert:(MEGAUserAlert *) case MEGAUserAlertTypePaymentFailed: { NSString *proPlanString = [userAlert stringAtIndex:0] ? [userAlert stringAtIndex:0] : @""; NSAttributedString *proPlan = [[NSAttributedString alloc] initWithString:proPlanString attributes:@{ NSFontAttributeName : boldFont }]; - NSString *text = NSLocalizedString(@"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."); + NSString *text = LocalizedString(@"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]; @@ -340,13 +340,13 @@ - (void)configureContentLabel:(UILabel *)contentLabel forAlert:(MEGAUserAlert *) NSInteger days = ([userAlert timestampAtIndex:1] - [NSDate date].timeIntervalSince1970) / secondsInADay; NSString *text; if (days == 1) { - text = NSLocalizedString(@"Your PRO membership plan will expire in 1 day.", @"The professional pricing plan which the user is currently on will expire in one day."); + text = LocalizedString(@"Your PRO membership plan will expire in 1 day.", @"The professional pricing plan which the user is currently on will expire in one day."); } else if (days >= 0) { - text = [NSLocalizedString(@"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:@"%td", days]]; + text = [LocalizedString(@"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:@"%td", days]]; } else if (days == -1) { - text = NSLocalizedString(@"Your PRO membership plan expired 1 day ago", @"The professional pricing plan which the user was on expired one day ago."); + text = LocalizedString(@"Your PRO membership plan expired 1 day ago", @"The professional pricing plan which the user was on expired one day ago."); } else { - text = [NSLocalizedString(@"Your PRO membership plan expired %1 days ago", @"The professional pricing plan which the user was on expired %1 days ago. The %1 is a placeholder for the number of days and should not be removed.") stringByReplacingOccurrencesOfString:@"%1" withString:[NSString stringWithFormat:@"%td", days]]; + text = [LocalizedString(@"Your PRO membership plan expired %1 days ago", @"The professional pricing plan which the user was on expired %1 days ago. The %1 is a placeholder for the number of days and should not be removed.") stringByReplacingOccurrencesOfString:@"%1" withString:[NSString stringWithFormat:@"%td", days]]; } contentLabel.text = text; break; @@ -437,7 +437,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath switch (userAlert.type) { case MEGAUserAlertTypeIncomingPendingContactRequest: case MEGAUserAlertTypeIncomingPendingContactReminder: { - if ([[MEGASdkManager sharedMEGASdk] incomingContactRequests].size.intValue) { + if ([[MEGASdk shared] incomingContactRequests].size) { ContactRequestsViewController *contactRequestsVC = [[UIStoryboard storyboardWithName:@"Contacts" bundle:nil] instantiateViewControllerWithIdentifier:@"ContactsRequestsViewControllerID"]; [self.navigationController pushViewController:contactRequestsVC animated:YES]; @@ -516,9 +516,9 @@ - (nullable UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView { - (NSString *)titleForEmptyState { NSString *text = @""; if ([MEGAReachabilityManager isReachable]) { - text = NSLocalizedString(@"No notifications", @"There are no notifications to display."); + text = LocalizedString(@"No notifications", @"There are no notifications to display."); } else { - text = NSLocalizedString(@"noInternetConnection", @"No Internet Connection"); + text = LocalizedString(@"noInternetConnection", @"No Internet Connection"); } return text; } @@ -526,7 +526,7 @@ - (NSString *)titleForEmptyState { - (NSString *)descriptionForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); + text = LocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); } return text; @@ -545,7 +545,7 @@ - (UIImage *)imageForEmptyState { - (NSString *)buttonTitleForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); + text = LocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); } return text; diff --git a/iMEGA/My Account/Notifications/ScheduleMeetingOccurrenceNotification.swift b/iMEGA/My Account/Notifications/ScheduleMeetingOccurrenceNotification.swift index 67f6c17a22..f07d42e4d8 100644 --- a/iMEGA/My Account/Notifications/ScheduleMeetingOccurrenceNotification.swift +++ b/iMEGA/My Account/Notifications/ScheduleMeetingOccurrenceNotification.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n final class ScheduleMeetingOccurrenceNotification: NSObject { // MARK: - Properties. diff --git a/iMEGA/My Account/Offline/OfflineCollectionViewController+ContextMenu.swift b/iMEGA/My Account/Offline/OfflineCollectionViewController+ContextMenu.swift index ff9c670b7b..d7cb3bc586 100644 --- a/iMEGA/My Account/Offline/OfflineCollectionViewController+ContextMenu.swift +++ b/iMEGA/My Account/Offline/OfflineCollectionViewController+ContextMenu.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGASwift extension OfflineCollectionViewController { diff --git a/iMEGA/My Account/Offline/OfflineCollectionViewController+DynamicType.swift b/iMEGA/My Account/Offline/OfflineCollectionViewController+DynamicType.swift index d4ed783c39..75ce149233 100644 --- a/iMEGA/My Account/Offline/OfflineCollectionViewController+DynamicType.swift +++ b/iMEGA/My Account/Offline/OfflineCollectionViewController+DynamicType.swift @@ -1,4 +1,3 @@ - extension OfflineCollectionViewController: DynamicTypeCollectionViewSizing { func provideSizingCell(for indexPath: IndexPath) -> UICollectionViewCell? { guard let collectionView = collectionView, @@ -8,7 +7,7 @@ extension OfflineCollectionViewController: DynamicTypeCollectionViewSizing { NodeCollectionViewCell.instantiateFromFileNib : NodeCollectionViewCell.instantiateFromFolderNib - cell.configureCell(forOfflineItem: item, itemPath: offline.currentOfflinePath.appending("kFileName"), allowedMultipleSelection: collectionView.allowsMultipleSelection, sdk: MEGASdkManager.sharedMEGASdk(), delegate: nil) + cell.configureCell(forOfflineItem: item, itemPath: offline.currentOfflinePath.appending("kFileName"), allowedMultipleSelection: collectionView.allowsMultipleSelection, sdk: .shared, delegate: nil) return cell } diff --git a/iMEGA/My Account/Offline/OfflineCollectionViewController.h b/iMEGA/My Account/Offline/OfflineCollectionViewController.h index ada3c47867..10fa70b80a 100644 --- a/iMEGA/My Account/Offline/OfflineCollectionViewController.h +++ b/iMEGA/My Account/Offline/OfflineCollectionViewController.h @@ -1,4 +1,3 @@ - #import #import "NodeCollectionViewCell.h" diff --git a/iMEGA/My Account/Offline/OfflineCollectionViewController.m b/iMEGA/My Account/Offline/OfflineCollectionViewController.m index 98e1353cbc..0149092915 100644 --- a/iMEGA/My Account/Offline/OfflineCollectionViewController.m +++ b/iMEGA/My Account/Offline/OfflineCollectionViewController.m @@ -1,4 +1,3 @@ - #import "OfflineCollectionViewController.h" #import "NSString+MNZCategory.h" diff --git a/iMEGA/My Account/Offline/OfflineTableViewCell.h b/iMEGA/My Account/Offline/OfflineTableViewCell.h index 320b686e80..70c4f5980d 100644 --- a/iMEGA/My Account/Offline/OfflineTableViewCell.h +++ b/iMEGA/My Account/Offline/OfflineTableViewCell.h @@ -1,4 +1,3 @@ - @interface OfflineTableViewCell : UITableViewCell @property (weak, nonatomic) IBOutlet UIImageView *thumbnailImageView; diff --git a/iMEGA/My Account/Offline/OfflineTableViewController+Additions.swift b/iMEGA/My Account/Offline/OfflineTableViewController+Additions.swift index 0a7050f0dd..c940636295 100644 --- a/iMEGA/My Account/Offline/OfflineTableViewController+Additions.swift +++ b/iMEGA/My Account/Offline/OfflineTableViewController+Additions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASwift extension OfflineTableViewViewController { diff --git a/iMEGA/My Account/Offline/OfflineTableViewViewController.h b/iMEGA/My Account/Offline/OfflineTableViewViewController.h index cd0ab75757..e2e8eb2c71 100644 --- a/iMEGA/My Account/Offline/OfflineTableViewViewController.h +++ b/iMEGA/My Account/Offline/OfflineTableViewViewController.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/My Account/Offline/OfflineTableViewViewController.m b/iMEGA/My Account/Offline/OfflineTableViewViewController.m index 934b82bf49..c8b2b01608 100644 --- a/iMEGA/My Account/Offline/OfflineTableViewViewController.m +++ b/iMEGA/My Account/Offline/OfflineTableViewViewController.m @@ -1,4 +1,3 @@ - #import "OfflineTableViewViewController.h" #import "NSDate+MNZCategory.h" diff --git a/iMEGA/My Account/Offline/OfflineViewController+Additions.swift b/iMEGA/My Account/Offline/OfflineViewController+Additions.swift index d690ad9199..b67b28d379 100644 --- a/iMEGA/My Account/Offline/OfflineViewController+Additions.swift +++ b/iMEGA/My Account/Offline/OfflineViewController+Additions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo extension OfflineViewController { @@ -43,6 +44,14 @@ extension OfflineViewController { viewModel.dispatch(.removeSubscriptions) } + @objc func selectedCountTitle() -> String { + guard let selectedCount = selectedItems?.count, + selectedCount > 0 else { + return Strings.Localizable.selectTitle + } + return Strings.Localizable.General.Format.itemsSelected(selectedCount) + } + // MARK: - Private private var screenTitle: String { diff --git a/iMEGA/My Account/Offline/OfflineViewController+ContextMenu.swift b/iMEGA/My Account/Offline/OfflineViewController+ContextMenu.swift index f64485adb7..8c1f712f2c 100644 --- a/iMEGA/My Account/Offline/OfflineViewController+ContextMenu.swift +++ b/iMEGA/My Account/Offline/OfflineViewController+ContextMenu.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo extension OfflineViewController: DisplayMenuDelegate { diff --git a/iMEGA/My Account/Offline/OfflineViewController.m b/iMEGA/My Account/Offline/OfflineViewController.m index b365edfaed..67fef67cd4 100644 --- a/iMEGA/My Account/Offline/OfflineViewController.m +++ b/iMEGA/My Account/Offline/OfflineViewController.m @@ -23,6 +23,7 @@ #import "OfflineTableViewCell.h" #import "UIViewController+MNZCategory.h" #import "NSArray+MNZCategory.h" +@import MEGAL10nObjc; @import MEGAUIKit; static NSString *kFileName = @"kFileName"; @@ -263,7 +264,7 @@ - (void)initTable { self.offlineTableView = [self.storyboard instantiateViewControllerWithIdentifier:@"OfflineTableID"]; self.offlineTableView.offline = self; - UIViewController *bannerContainerVC = [[BannerContainerViewRouter.alloc initWithContentViewController:self.offlineTableView bannerMessage:NSLocalizedString(@"offline.logOut.warning.message", @"Offline log out warning message") bannerType:BannerTypeWarning] build]; + UIViewController *bannerContainerVC = [[BannerContainerViewRouter.alloc initWithContentViewController:self.offlineTableView bannerMessage:LocalizedString(@"offline.logOut.warning.message", @"Offline log out warning message") bannerType:BannerTypeWarning] build]; [self add:bannerContainerVC container:self.containerView animate:NO]; self.offlineTableView.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; @@ -287,7 +288,7 @@ - (void)initCollection { self.offlineCollectionView = [self.storyboard instantiateViewControllerWithIdentifier:@"OfflineCollectionID"]; self.offlineCollectionView.offline = self; - UIViewController *bannerContainerVC = [[BannerContainerViewRouter.alloc initWithContentViewController:self.offlineCollectionView bannerMessage:NSLocalizedString(@"offline.logOut.warning.message", @"Offline log out warning message") bannerType:BannerTypeWarning] build]; + UIViewController *bannerContainerVC = [[BannerContainerViewRouter.alloc initWithContentViewController:self.offlineCollectionView bannerMessage:LocalizedString(@"offline.logOut.warning.message", @"Offline log out warning message") bannerType:BannerTypeWarning] build]; [self add:bannerContainerVC container:self.containerView animate:NO]; self.offlineCollectionView.collectionView.emptyDataSetDelegate = self; @@ -783,7 +784,7 @@ - (void)setViewEditing:(BOOL)editing { if (editing) { self.navigationItem.rightBarButtonItem = self.editBarButtonItem; - self.editBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.editBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); self.navigationItem.leftBarButtonItems = @[self.selectAllBarButtonItem]; UITabBar *tabBar = self.tabBarController.tabBar; @@ -888,14 +889,10 @@ - (BOOL)removeOfflineNodeCell:(NSString *)itemPath { - (void)updateNavigationBarTitle { NSString *navigationTitle; if (self.offlineTableView.tableView.isEditing || self.offlineCollectionView.collectionView.allowsMultipleSelection) { - if (self.selectedItems.count == 0) { - navigationTitle = NSLocalizedString(@"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:NSLocalizedString(@"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:NSLocalizedString(@"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]; - } + navigationTitle = [self selectedCountTitle]; } else { if (self.folderPathFromOffline == nil) { - navigationTitle = NSLocalizedString(@"offline", @"Offline"); + navigationTitle = LocalizedString(@"offline", @"Offline"); } else { navigationTitle = self.folderPathFromOffline.lastPathComponent; } @@ -927,16 +924,16 @@ - (NSString *)currentOfflinePath { - (void)showRemoveAlertWithConfirmAction:(void (^)(void))confirmAction andCancelAction:(void (^ _Nullable)(void))cancelAction{ NSString *message; if (self.selectedItems.count > 1) { - message = NSLocalizedString(@"removeItemsFromOffline", nil); + message = LocalizedString(@"removeItemsFromOffline", @""); } else { - message = NSLocalizedString(@"removeItemFromOffline", nil); + message = LocalizedString(@"removeItemFromOffline", @""); } - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"remove", nil) message:message preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"remove", @"") message:message preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { confirmAction(); }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { if (cancelAction) { cancelAction(); } @@ -948,7 +945,7 @@ - (void)showInfoFilePath:(NSString *)itemPath at:(NSIndexPath *)indexPath from:( __weak __typeof__(self) weakSelf = self; NSMutableArray *actions = NSMutableArray.new; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"remove", @"Title for the action that allows to remove a file or folder") detail:nil image:[UIImage imageNamed:@"rubbishBin"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"remove", @"Title for the action that allows to remove a file or folder") detail:nil image:[UIImage imageNamed:@"rubbishBin"] style:UIAlertActionStyleDefault actionHandler:^{ [self showRemoveAlertWithConfirmAction:^{ [self removeOfflineNodeCell:itemPath]; } andCancelAction:nil]; @@ -958,9 +955,9 @@ - (void)showInfoFilePath:(NSString *)itemPath at:(NSIndexPath *)indexPath from:( NSString *title; BOOL fileExistsAtPath = [[NSFileManager defaultManager] fileExistsAtPath:itemPath isDirectory:&isDirectory]; if (isDirectory) { - title = NSLocalizedString(@"general.export", @"Button title which, if tapped, will trigger the action to export something from MEGA with the objective of sharing it outside of the app"); + title = LocalizedString(@"general.export", @"Button title which, if tapped, will trigger the action to export something from MEGA with the objective of sharing it outside of the app"); } else { - NSString *exportFileFormat = NSLocalizedString(@"general.menuAction.exportFile.title", @"Button title which, if tapped, will trigger the action of downloading the node and after that the user will be able to share through the iOS share menu"); + NSString *exportFileFormat = LocalizedString(@"general.menuAction.exportFile.title", @"Button title which, if tapped, will trigger the action of downloading the node and after that the user will be able to share through the iOS share menu"); title = [NSString stringWithFormat:exportFileFormat, 1]; } if (fileExistsAtPath) { @@ -1037,13 +1034,13 @@ - (NSString *)titleForEmptyState { NSString *text = @""; if (self.searchController.isActive) { if (self.searchController.searchBar.text.length > 0) { - text = NSLocalizedString(@"noResults", @"Title shown when you make a search and there is 'No Results'"); + text = LocalizedString(@"noResults", @"Title shown when you make a search and there is 'No Results'"); } } else { if (self.folderPathFromOffline) { - text = NSLocalizedString(@"emptyFolder", @"Title shown when a folder doesn't have any files"); + text = LocalizedString(@"emptyFolder", @"Title shown when a folder doesn't have any files"); } else { - text = NSLocalizedString(@"offlineEmptyState_title", @"Title shown when the Offline section is empty, when you don't have download any files. Keep the upper."); + text = LocalizedString(@"offlineEmptyState_title", @"Title shown when the Offline section is empty, when you don't have download any files. Keep the upper."); } } diff --git a/iMEGA/My Account/PSA/PSAView.swift b/iMEGA/My Account/PSA/PSAView.swift index 15d4309ec5..d033db25f6 100644 --- a/iMEGA/My Account/PSA/PSAView.swift +++ b/iMEGA/My Account/PSA/PSAView.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation import MEGAUIKit import UIKit diff --git a/iMEGA/My Account/Profile/ChangeCredentials/ChangePasswordViewController.m b/iMEGA/My Account/Profile/ChangeCredentials/ChangePasswordViewController.m index a76b0ea039..f39250ab72 100644 --- a/iMEGA/My Account/Profile/ChangeCredentials/ChangePasswordViewController.m +++ b/iMEGA/My Account/Profile/ChangeCredentials/ChangePasswordViewController.m @@ -1,4 +1,3 @@ - #import "ChangePasswordViewController.h" #import "MEGASdkManager.h" @@ -16,6 +15,7 @@ #import "PasswordView.h" #import "TwoFactorAuthenticationViewController.h" @import MEGASDKRepo; +@import MEGAL10nObjc; typedef NS_ENUM(NSUInteger, TextFieldTag) { CurrentEmailTextFieldTag = 0, @@ -54,13 +54,13 @@ @implementation ChangePasswordViewController - (void)viewDidLoad { [super viewDidLoad]; - self.confirmButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"save", @"save password or email associated to an account.") style:UIBarButtonItemStylePlain target:self action:@selector(confirmButtonTouchUpInside:)]; + self.confirmButton = [[UIBarButtonItem alloc] initWithTitle:LocalizedString(@"save", @"save password or email associated to an account.") style:UIBarButtonItemStylePlain target:self action:@selector(confirmButtonTouchUpInside:)]; self.navigationItem.rightBarButtonItem = self.confirmButton; switch (self.changeType) { case ChangeTypePassword: case ChangeTypePasswordFromLogout: - self.navigationItem.title = NSLocalizedString(@"changePasswordLabel", @"Section title where you can change your MEGA's password"); + self.navigationItem.title = LocalizedString(@"changePasswordLabel", @"Section title where you can change your MEGA's password"); self.theNewPasswordView.passwordTextField.returnKeyType = UIReturnKeyNext; self.theNewPasswordView.passwordTextField.delegate = self; @@ -75,7 +75,7 @@ - (void)viewDidLoad { break; case ChangeTypeEmail: { - self.navigationItem.title = NSLocalizedString(@"Change Email", @"The title of the alert dialog to change the email associated to an account."); + self.navigationItem.title = LocalizedString(@"Change Email", @"The title of the alert dialog to change the email associated to an account."); self.theNewPasswordView.hidden = self.confirmPasswordView.hidden = YES; self.currentEmailInputView.hidden = self.theNewEmailInputView.hidden = NO; @@ -95,7 +95,7 @@ - (void)viewDidLoad { case ChangeTypeResetPassword: case ChangeTypeParkAccount: - self.navigationItem.title = (self.changeType == ChangeTypeResetPassword) ? NSLocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") : NSLocalizedString(@"parkAccount", @"Headline for parking an account (basically restarting from scratch)"); + self.navigationItem.title = (self.changeType == ChangeTypeResetPassword) ? LocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") : LocalizedString(@"parkAccount", @"Headline for parking an account (basically restarting from scratch)"); self.currentEmailInputView.hidden = NO; self.currentEmailInputView.inputTextField.text = self.email; @@ -221,26 +221,26 @@ - (BOOL)validateForm { - (BOOL)validateNewPassword { if (self.theNewPasswordView.passwordTextField.text.mnz_isEmpty) { - [self.theNewPasswordView setErrorState:YES withText:NSLocalizedString(@"passwordInvalidFormat", @"Message shown when the user enters a wrong password")]; + [self.theNewPasswordView setErrorState:YES withText:LocalizedString(@"passwordInvalidFormat", @"Message shown when the user enters a wrong password")]; return NO; } else if ([MEGASdkManager.sharedMEGASdk checkPassword:self.theNewPasswordView.passwordTextField.text]) { - [self.theNewPasswordView setErrorState:YES withText:NSLocalizedString(@"account.changePassword.error.currentPassword", @"Account, Change Password view. Error shown when you type your current password.")]; + [self.theNewPasswordView setErrorState:YES withText:LocalizedString(@"account.changePassword.error.currentPassword", @"Account, Change Password view. Error shown when you type your current password.")]; return NO; } else if ([[MEGASdkManager sharedMEGASdk] passwordStrength:self.theNewPasswordView.passwordTextField.text] == PasswordStrengthVeryWeak) { - [self.theNewPasswordView setErrorState:YES withText:NSLocalizedString(@"pleaseStrengthenYourPassword", nil)]; + [self.theNewPasswordView setErrorState:YES withText:LocalizedString(@"pleaseStrengthenYourPassword", @"")]; return NO; } else { - [self.theNewPasswordView setErrorState:NO withText:NSLocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password")]; + [self.theNewPasswordView setErrorState:NO withText:LocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password")]; return YES; } } - (BOOL)validateConfirmPassword { if ([self.confirmPasswordView.passwordTextField.text isEqualToString:self.theNewPasswordView.passwordTextField.text]) { - [self.confirmPasswordView setErrorState:NO withText:NSLocalizedString(@"confirmPassword", @"Hint text where the user have to re-write the new password to confirm it")]; + [self.confirmPasswordView setErrorState:NO withText:LocalizedString(@"confirmPassword", @"Hint text where the user have to re-write the new password to confirm it")]; return YES; } else { - [self.confirmPasswordView setErrorState:YES withText:NSLocalizedString(@"passwordsDoNotMatch", @"Error text shown when you have not written the same password")]; + [self.confirmPasswordView setErrorState:YES withText:LocalizedString(@"passwordsDoNotMatch", @"Error text shown when you have not written the same password")]; return NO; } } @@ -248,13 +248,13 @@ - (BOOL)validateConfirmPassword { - (BOOL)validateEmail { self.theNewEmailInputView.inputTextField.text = self.theNewEmailInputView.inputTextField.text.mnz_removeWhitespacesAndNewlinesFromBothEnds; if (!self.theNewEmailInputView.inputTextField.text.mnz_isValidEmail) { - [self.theNewEmailInputView setErrorState:YES withText:NSLocalizedString(@"emailInvalidFormat", @"Message shown when the user writes an invalid format in the email field")]; + [self.theNewEmailInputView setErrorState:YES withText:LocalizedString(@"emailInvalidFormat", @"Message shown when the user writes an invalid format in the email field")]; return NO; } else if ([self.theNewEmailInputView.inputTextField.text isEqualToString:self.currentEmailInputView.inputTextField.text]) { - [self.theNewEmailInputView setErrorState:YES withText:NSLocalizedString(@"oldAndNewEmailMatch", @"Error message shown when the users tryes to change his/her email and writes the current one as the new one.")]; + [self.theNewEmailInputView setErrorState:YES withText:LocalizedString(@"oldAndNewEmailMatch", @"Error message shown when the users tryes to change his/her email and writes the current one as the new one.")]; return NO; } else { - [self.theNewEmailInputView setErrorState:NO withText:NSLocalizedString(@"newEmail", @"Placeholder text to explain that the new email should be written on this text field.")]; + [self.theNewEmailInputView setErrorState:NO withText:LocalizedString(@"newEmail", @"Placeholder text to explain that the new email should be written on this text field.")]; return YES; } } @@ -271,8 +271,8 @@ - (void)processStarted { [[NSNotificationCenter defaultCenter] removeObserver:self name:@"processStarted" object:nil]; AwaitingEmailConfirmationView *awaitingEmailConfirmationView = [[[NSBundle mainBundle] loadNibNamed:@"AwaitingEmailConfirmationView" owner:self options: nil] firstObject]; - awaitingEmailConfirmationView.titleLabel.text = NSLocalizedString(@"awaitingEmailConfirmation", @"Title shown just after doing some action that requires confirming the action by an email"); - awaitingEmailConfirmationView.descriptionLabel.text = NSLocalizedString(@"emailIsChanging_description", @"Text shown just after tap to change an email account to remenber the user what to do to complete the change email proccess"); + awaitingEmailConfirmationView.titleLabel.text = LocalizedString(@"awaitingEmailConfirmation", @"Title shown just after doing some action that requires confirming the action by an email"); + awaitingEmailConfirmationView.descriptionLabel.text = LocalizedString(@"emailIsChanging_description", @"Text shown just after tap to change an email account to remenber the user what to do to complete the change email proccess"); awaitingEmailConfirmationView.frame = self.view.bounds; self.view = awaitingEmailConfirmationView; @@ -364,9 +364,9 @@ - (IBAction)confirmButtonTouchUpInside:(UIButton *)sender { break; case ChangeTypeParkAccount: { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"startNewAccount", @"Headline of the password reset recovery procedure") message:NSLocalizedString(@"startingFreshAccount", @"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')") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"startNewAccount", @"Headline of the password reset recovery procedure") message:LocalizedString(@"startingFreshAccount", @"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')") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [[MEGASdkManager sharedMEGASdk] confirmResetPasswordWithLink:self.link newPassword:self.theNewPasswordView.passwordTextField.text masterKey:nil delegate:self]; }]]; [self presentViewController:alertController animated:YES completion:nil]; @@ -439,16 +439,16 @@ - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRang switch (textField.tag) { case NewEmailTextFieldTag: - [self.theNewEmailInputView setErrorState:NO withText:NSLocalizedString(@"newEmail", @"Placeholder text to explain that the new email should be written on this text field.")]; + [self.theNewEmailInputView setErrorState:NO withText:LocalizedString(@"newEmail", @"Placeholder text to explain that the new email should be written on this text field.")]; break; case NewPasswordTextFieldTag: - [self.theNewPasswordView setErrorState:NO withText:NSLocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password")]; + [self.theNewPasswordView setErrorState:NO withText:LocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password")]; break; case ConfirmPasswordTextFieldTag: - [self.confirmPasswordView setErrorState:NO withText:NSLocalizedString(@"confirmPassword", @"Hint text where the user have to re-write the new password to confirm it")]; + [self.confirmPasswordView setErrorState:NO withText:LocalizedString(@"confirmPassword", @"Hint text where the user have to re-write the new password to confirm it")]; break; default: @@ -541,7 +541,7 @@ - (void)onUsersUpdate:(MEGASdk *)api userList:(MEGAUserList *)userList { for (NSInteger i = 0 ; i < count; i++) { MEGAUser *user = [userList userAtIndex:i]; if (user.handle == MEGASdk.currentUserHandle.unsignedLongLongValue && user.changes == MEGAUserChangeTypeEmail) { - NSString *emailChangedString = [NSLocalizedString(@"congratulationsNewEmailAddress", @"The [X] will be replaced with the e-mail address.") stringByReplacingOccurrencesOfString:@"[X]" withString:user.email]; + NSString *emailChangedString = [LocalizedString(@"congratulationsNewEmailAddress", @"The [X] will be replaced with the e-mail address.") stringByReplacingOccurrencesOfString:@"[X]" withString:user.email]; [SVProgressHUD showSuccessWithStatus:emailChangedString]; [self dismissViewControllerAnimated:YES completion:nil]; } @@ -556,7 +556,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG switch (error.type) { case MEGAErrorTypeApiEArgs: { if (request.type == MEGARequestTypeChangePassword) { - [self.theNewPasswordView setErrorState:YES withText:NSLocalizedString(@"passwordInvalidFormat", @"Message shown when the user enters a wrong password")]; + [self.theNewPasswordView setErrorState:YES withText:LocalizedString(@"passwordInvalidFormat", @"Message shown when the user enters a wrong password")]; [self.theNewPasswordView.passwordTextField becomeFirstResponder]; } break; @@ -564,8 +564,8 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGAErrorTypeApiEExist: { if (request.type == MEGARequestTypeGetChangeEmailLink) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"emailAddressChangeAlreadyRequested", @"Error message shown when you try to change your account email to one that you already requested.") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"emailAddressChangeAlreadyRequested", @"Error message shown when you try to change your account email to one that you already requested.") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; } break; @@ -573,23 +573,23 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGAErrorTypeApiEKey: { if (request.type == MEGARequestTypeConfirmRecoveryLink) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"invalidRecoveryKey", @"An alert title where the user provided the incorrect Recovery Key.") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") message:NSLocalizedString(@"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]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"invalidRecoveryKey", @"An alert title where the user provided the incorrect Recovery Key.") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"passwordReset", @"Headline of the password reset recovery procedure") message:LocalizedString(@"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]; [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { - textField.placeholder = NSLocalizedString(@"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.placeholder = LocalizedString(@"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 becomeFirstResponder]; [textField addTarget:self action:@selector(alertTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { return !textField.text.mnz_isEmpty; }; }]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { UITextField *textField = alertController.textFields.firstObject; [textField resignFirstResponder]; [self dismissViewControllerAnimated:YES completion:nil]; }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { self.masterKey = alertController.textFields.firstObject.text; [self.theNewEmailInputView.inputTextField becomeFirstResponder]; }]]; @@ -604,7 +604,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGAErrorTypeApiEAccess: if (request.type == MEGARequestTypeGetChangeEmailLink) { - [self.theNewEmailInputView setErrorState:YES withText:NSLocalizedString(@"emailAlreadyInUse", @"Error shown when the user tries to change his mail to one that is already used")]; + [self.theNewEmailInputView setErrorState:YES withText:LocalizedString(@"emailAlreadyInUse", @"Error shown when the user tries to change his mail to one that is already used")]; [self.theNewEmailInputView.inputTextField becomeFirstResponder]; } break; @@ -617,7 +617,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG switch (request.type) { case MEGARequestTypeChangePassword: { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"passwordChanged", @"The label showed when your password has been changed")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"passwordChanged", @"The label showed when your password has been changed")]; if (self.changeType == ChangeTypePassword) { [self.navigationController dismissViewControllerAnimated:YES completion:nil]; @@ -635,7 +635,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGARequestTypeConfirmRecoveryLink: { if (self.changeType == ChangeTypePassword) { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"passwordChanged", @"The label showed when your password has been changed")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"passwordChanged", @"The label showed when your password has been changed")]; [self dismissViewControllerAnimated:YES completion:nil]; } else { [self.view endEditing:YES]; @@ -645,7 +645,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (self.changeType == ChangeTypeResetPassword) { if ([[MEGASdkManager sharedMEGASdk] isLoggedIn]) { [[NSNotificationCenter defaultCenter] postNotificationName:@"passwordReset" object:nil]; - title = NSLocalizedString(@"passwordChanged", @"The label showed when your password has been changed"); + title = LocalizedString(@"passwordChanged", @"The label showed when your password has been changed"); completion = ^{ if (self.link) { @@ -655,21 +655,21 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG } }; } else { - title = NSLocalizedString(@"yourPasswordHasBeenReset", nil); + title = LocalizedString(@"yourPasswordHasBeenReset", @""); completion = ^{ [self.navigationController dismissViewControllerAnimated:YES completion:nil]; }; } } else if (self.changeType == ChangeTypeParkAccount) { - title = NSLocalizedString(@"yourAccounHasBeenParked", nil); + title = LocalizedString(@"yourAccounHasBeenParked", @""); completion = ^{ [self.navigationController dismissViewControllerAnimated:YES completion:nil]; }; } UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { completion(); }]]; diff --git a/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController+Additions.swift b/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController+Additions.swift index bb897afac4..6b655d56f0 100644 --- a/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController+Additions.swift +++ b/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController+Additions.swift @@ -1,4 +1,6 @@ import MEGADomain +import MEGAL10n +import MEGASDKRepo import MEGASwift extension ChangeNameViewController: UITextFieldDelegate { @@ -33,7 +35,7 @@ extension ChangeNameViewController: UITextFieldDelegate { SVProgressHUD.showSuccess(withStatus: Strings.Localizable.youHaveSuccessfullyChangedYourProfile) dismiss(animated: true) } catch let megaError as MEGAError { - SVProgressHUD.showError(withStatus: NSLocalizedString(megaError.name, comment: "")) + SVProgressHUD.showError(withStatus: Strings.localized(megaError.name, comment: "")) } catch { SVProgressHUD.showError(withStatus: error.localizedDescription) } diff --git a/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController.h b/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController.h index dbb6e21406..95d55a1ad2 100644 --- a/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController.h +++ b/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController.h @@ -1,4 +1,3 @@ - @interface ChangeNameViewController : UIViewController @property (weak, nonatomic) IBOutlet UITextField *firstNameTextField; diff --git a/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController.m b/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController.m index a2e16679ad..88234e0f3f 100644 --- a/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController.m +++ b/iMEGA/My Account/Profile/ChangeName/ChangeNameViewController.m @@ -10,6 +10,8 @@ #import "NSString+MNZCategory.h" @import MEGASDKRepo; +@import MEGAL10nObjc; + @interface ChangeNameViewController () @property (weak, nonatomic) IBOutlet UIView *firstNameView; @@ -33,24 +35,24 @@ @implementation ChangeNameViewController - (void)viewDidLoad { [super viewDidLoad]; - self.navigationItem.title = NSLocalizedString(@"changeName", @"Button title that allows the user change his name"); + self.navigationItem.title = LocalizedString(@"changeName", @"Button title that allows the user change his name"); MOUser *moUser = [[MEGAStore shareInstance] fetchUserWithUserHandle:MEGASdk.currentUserHandle.unsignedLongLongValue]; self.firstName = moUser.firstname; self.lastName = moUser.lastname; - self.firstName ? (self.firstNameTextField.text = self.firstName) : (self.firstNameTextField.placeholder = NSLocalizedString(@"firstName", @"Hint text for the first name (Placeholder)")); - self.lastName ? (self.lastNameTextField.text = self.lastName) : (self.lastNameTextField.placeholder = NSLocalizedString(@"lastName", @"Hint text for the last name (Placeholder)")); + self.firstName ? (self.firstNameTextField.text = self.firstName) : (self.firstNameTextField.placeholder = LocalizedString(@"firstName", @"Hint text for the first name (Placeholder)")); + self.lastName ? (self.lastNameTextField.text = self.lastName) : (self.lastNameTextField.placeholder = LocalizedString(@"lastName", @"Hint text for the last name (Placeholder)")); self.firstNameTextField.textContentType = UITextContentTypeGivenName; self.lastNameTextField.textContentType = UITextContentTypeFamilyName; - self.cancelBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); - [self.saveButton setTitle:NSLocalizedString(@"save", @"Button title to 'Save' the selected option")]; + self.cancelBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); + [self.saveButton setTitle:LocalizedString(@"save", @"Button title to 'Save' the selected option")]; [self.saveButton setTitleTextAttributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleBody weight:UIFontWeightMedium]} forState:UIControlStateNormal]; - self.firstNameLabel.text = NSLocalizedString(@"firstName", @"Hint text for the first name (Placeholder)"); - self.lastNameLabel.text = NSLocalizedString(@"lastName", @"Hint text for the first name (Placeholder)"); + self.firstNameLabel.text = LocalizedString(@"firstName", @"Hint text for the first name (Placeholder)"); + self.lastNameLabel.text = LocalizedString(@"lastName", @"Hint text for the first name (Placeholder)"); [self updateAppearance]; } diff --git a/iMEGA/My Account/Profile/LogoutTableViewCell.swift b/iMEGA/My Account/Profile/LogoutTableViewCell.swift index 297d1efb3d..1189b29449 100644 --- a/iMEGA/My Account/Profile/LogoutTableViewCell.swift +++ b/iMEGA/My Account/Profile/LogoutTableViewCell.swift @@ -1,4 +1,3 @@ - import UIKit class LogoutTableViewCell: UITableViewCell { diff --git a/iMEGA/My Account/Profile/PhoneNumberViewController.swift b/iMEGA/My Account/Profile/PhoneNumberViewController.swift index 931215b778..0838d34c01 100644 --- a/iMEGA/My Account/Profile/PhoneNumberViewController.swift +++ b/iMEGA/My Account/Profile/PhoneNumberViewController.swift @@ -1,4 +1,5 @@ - +import MEGAL10n +import MEGASDKRepo import PhoneNumberKit import UIKit @@ -33,7 +34,7 @@ class PhoneNumberViewController: UITableViewController { modifyNumberLabel.text = Strings.Localizable.modifyPhoneNumber removeNumberLabel.text = Strings.Localizable.removePhoneNumber - guard let verifiedPhone = MEGASdkManager.sharedMEGASdk().smsVerifiedPhoneNumber() else { + guard let verifiedPhone = MEGASdk.shared.smsVerifiedPhoneNumber() else { fatalError("Can not fetch verified phone number") } @@ -68,8 +69,8 @@ class PhoneNumberViewController: UITableViewController { private func showModifyPhoneAlert() { let modifyPhoneNumberAlert = UIAlertController(title: Strings.Localizable.modifyPhoneNumber, message: Strings.Localizable.thisOperationWillRemoveYourCurrentPhoneNumberAndStartTheProcessOfAssociatingANewPhoneNumberWithYourAccount, preferredStyle: .alert) modifyPhoneNumberAlert.addAction(UIAlertAction(title: Strings.Localizable.ok, style: .default, handler: { _ in - MEGASdkManager.sharedMEGASdk().resetSmsVerifiedPhoneNumber(with: MEGAGenericRequestDelegate(completion: { (_, error) in - if error.type == .apiOk { + MEGASdk.shared.resetSmsVerifiedPhoneNumber(with: RequestDelegate { result in + if case .success = result { let presenter = self.presentingViewController self.dismiss(animated: true, completion: { if let presenter = presenter { @@ -79,7 +80,7 @@ class PhoneNumberViewController: UITableViewController { } else { SVProgressHUD.showError(withStatus: Strings.Localizable.failedToRemoveYourPhoneNumberPleaseTryAgainLater) } - })) + }) })) modifyPhoneNumberAlert.addAction(UIAlertAction(title: Strings.Localizable.cancel, style: .cancel, handler: nil)) present(modifyPhoneNumberAlert, animated: true, completion: nil) @@ -88,15 +89,15 @@ class PhoneNumberViewController: UITableViewController { private func showRemovePhoneAlert() { let removePhoneNumberAlert = UIAlertController(title: Strings.Localizable.removePhoneNumber, message: Strings.Localizable.ThisWillRemoveYourAssociatedPhoneNumberFromYourAccount.ifYouLaterChooseToAddAPhoneNumberYouWillBeRequiredToVerifyIt, preferredStyle: .alert) removePhoneNumberAlert.addAction(UIAlertAction(title: Strings.Localizable.ok, style: .default, handler: { _ in - MEGASdkManager.sharedMEGASdk().resetSmsVerifiedPhoneNumber(with: MEGAGenericRequestDelegate(completion: { (_, error) in - if error.type == .apiOk { + MEGASdk.shared.resetSmsVerifiedPhoneNumber(with: RequestDelegate { result in + if case .success = result { self.dismiss(animated: true, completion: { SVProgressHUD.showInfo(withStatus: Strings.Localizable.yourPhoneNumberHasBeenRemovedSuccessfully) }) } else { SVProgressHUD.showError(withStatus: Strings.Localizable.failedToRemoveYourPhoneNumberPleaseTryAgainLater) } - })) + }) })) removePhoneNumberAlert.addAction(UIAlertAction(title: Strings.Localizable.cancel, style: .cancel, handler: nil)) present(removePhoneNumberAlert, animated: true, completion: nil) diff --git a/iMEGA/My Account/Profile/ProfileTableViewCell.swift b/iMEGA/My Account/Profile/ProfileTableViewCell.swift index e1ffa0dfd4..73059cee30 100644 --- a/iMEGA/My Account/Profile/ProfileTableViewCell.swift +++ b/iMEGA/My Account/Profile/ProfileTableViewCell.swift @@ -1,4 +1,3 @@ - import UIKit class ProfileTableViewCell: UITableViewCell { diff --git a/iMEGA/My Account/Profile/ProfileTableViewDataSource.swift b/iMEGA/My Account/Profile/ProfileTableViewDataSource.swift index e398c6f7ca..de3d115694 100644 --- a/iMEGA/My Account/Profile/ProfileTableViewDataSource.swift +++ b/iMEGA/My Account/Profile/ProfileTableViewDataSource.swift @@ -1,5 +1,6 @@ import MEGADomain import MEGAFoundation +import MEGAL10n import PhoneNumberKit import UIKit @@ -74,11 +75,11 @@ final class ProfileTableViewDataSource { let cell = tableView.dequeueReusableCell(withIdentifier: "ProfileCellID", for: indexPath) as! ProfileTableViewCell cell.accessoryType = .disclosureIndicator cell.detailLabel.text = "" - if MEGASdkManager.sharedMEGASdk().smsVerifiedPhoneNumber() == nil { + if MEGASdk.shared.smsVerifiedPhoneNumber() == nil { cell.nameLabel.text = Strings.Localizable.addPhoneNumber } else { cell.nameLabel.text = Strings.Localizable.phoneNumber - let phoneNumber = MEGASdkManager.sharedMEGASdk().smsVerifiedPhoneNumber() + let phoneNumber = MEGASdk.shared.smsVerifiedPhoneNumber() do { let phone = try PhoneNumberKit().parse(phoneNumber ?? "") cell.detailLabel.text = PhoneNumberKit().format(phone, toType: .international) @@ -108,7 +109,7 @@ final class ProfileTableViewDataSource { cell.selectionStyle = .default cell.accessoryType = MEGAPurchase.sharedInstance()?.products?.count ?? 0 > 0 ? .disclosureIndicator : .none - guard let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails else { + guard let accountDetails = MEGASdk.shared.mnz_accountDetails else { return cell } @@ -131,7 +132,7 @@ final class ProfileTableViewDataSource { cell.detailLabel.text = Strings.Localizable.proLite cell.detailLabel.textColor = UIColor.systemOrange case .business: - if MEGASdkManager.sharedMEGASdk().businessStatus == .active { + if MEGASdk.shared.businessStatus == .active { cell.detailLabel.text = Strings.Localizable.active } else { cell.detailLabel.text = Strings.Localizable.paymentOverdue @@ -153,7 +154,7 @@ final class ProfileTableViewDataSource { cell.selectionStyle = .default cell.accessoryType = MEGAPurchase.sharedInstance()?.products?.count ?? 0 > 0 ? .disclosureIndicator : .none - if MEGASdkManager.sharedMEGASdk().isMasterBusinessAccount { + if MEGASdk.shared.isMasterBusinessAccount { cell.detailLabel.text = Strings.Localizable.administrator } else { cell.detailLabel.text = Strings.Localizable.user diff --git a/iMEGA/My Account/Profile/ProfileTableViewDiffableDataSource.swift b/iMEGA/My Account/Profile/ProfileTableViewDiffableDataSource.swift index 580a514808..4abaa98274 100644 --- a/iMEGA/My Account/Profile/ProfileTableViewDiffableDataSource.swift +++ b/iMEGA/My Account/Profile/ProfileTableViewDiffableDataSource.swift @@ -1,4 +1,6 @@ import MEGAFoundation +import MEGAL10n +import MEGASDKRepo import UIKit final class ProfileTableViewDiffableDataSource: UITableViewDiffableDataSource { @@ -19,7 +21,7 @@ final class ProfileTableViewDiffableDataSource: UITableViewDiffableDataSource @@ -9,6 +8,8 @@ #import "MEGA-Swift.h" #import "NSString+MNZCategory.h" +@import MEGAL10nObjc; + @interface AdvancedTableViewController () @property (weak, nonatomic) IBOutlet UILabel *savePhotosLabel; @@ -32,7 +33,7 @@ @implementation AdvancedTableViewController - (void)viewDidLoad { [super viewDidLoad]; - [self.navigationItem setTitle:NSLocalizedString(@"advanced", nil)]; + [self.navigationItem setTitle:LocalizedString(@"advanced", @"")]; [self checkAuthorizationStatus]; @@ -42,10 +43,10 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - [self.dontUseHttpLabel setText:NSLocalizedString(@"dontUseHttp", @"Text next to a switch that allows disabling the HTTP protocol for transfers")]; - self.savePhotosLabel.text = NSLocalizedString(@"Save Images in Photos", @"Settings section title where you can enable the option to 'Save Images in Photos'"); - self.saveVideosLabel.text = NSLocalizedString(@"Save Videos in Photos", @"Settings section title where you can enable the option to 'Save Videos in Photos'"); - self.saveMediaInGalleryLabel.text = NSLocalizedString(@"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"); + [self.dontUseHttpLabel setText:LocalizedString(@"dontUseHttp", @"Text next to a switch that allows disabling the HTTP protocol for transfers")]; + self.savePhotosLabel.text = LocalizedString(@"Save Images in Photos", @"Settings section title where you can enable the option to 'Save Images in Photos'"); + self.saveVideosLabel.text = LocalizedString(@"Save Videos in Photos", @"Settings section title where you can enable the option to 'Save Videos in Photos'"); + self.saveMediaInGalleryLabel.text = LocalizedString(@"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:MEGAGroupIdentifier] boolForKey:@"useHttpsOnly"]; [self.useHttpsOnlySwitch setOn:useHttpsOnly]; diff --git a/iMEGA/My Account/Settings/Appearance/AppearanceTableViewController.swift b/iMEGA/My Account/Settings/Appearance/AppearanceTableViewController.swift index c5e8228fcb..64b6fd8e58 100644 --- a/iMEGA/My Account/Settings/Appearance/AppearanceTableViewController.swift +++ b/iMEGA/My Account/Settings/Appearance/AppearanceTableViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit enum AppearanceSection: Int { diff --git a/iMEGA/My Account/Settings/Appearance/DefaultTabTableViewController.swift b/iMEGA/My Account/Settings/Appearance/DefaultTabTableViewController.swift index 674ef06dc5..33643eca57 100644 --- a/iMEGA/My Account/Settings/Appearance/DefaultTabTableViewController.swift +++ b/iMEGA/My Account/Settings/Appearance/DefaultTabTableViewController.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit final class DefaultTabTableViewController: UITableViewController { @@ -24,7 +24,7 @@ final class DefaultTabTableViewController: UITableViewController { let tab = Tab(tabType: tabType) cell.imageView?.image = tab.icon?.withTintColor(UIColor.mnz_primaryGray(for: traitCollection)) let title = tab.title - cell.textLabel?.text = NSLocalizedString(title, comment: title) + cell.textLabel?.text = Strings.localized(title, comment: title) cell.textLabel?.font = UIFont.preferredFont(forTextStyle: .body) } cell.accessoryView = UIImageView(image: Asset.Images.Generic.turquoiseCheckmark.image) diff --git a/iMEGA/My Account/Settings/Appearance/SortingAndViewModeTableViewController.swift b/iMEGA/My Account/Settings/Appearance/SortingAndViewModeTableViewController.swift index f88bc27de4..435fcb9a0a 100644 --- a/iMEGA/My Account/Settings/Appearance/SortingAndViewModeTableViewController.swift +++ b/iMEGA/My Account/Settings/Appearance/SortingAndViewModeTableViewController.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit enum SortingAndViewSection: Int { diff --git a/iMEGA/My Account/Settings/Calls/CallsSettingsViewRouter.swift b/iMEGA/My Account/Settings/Calls/CallsSettingsViewRouter.swift index 9ae8ca7299..55651bc75b 100644 --- a/iMEGA/My Account/Settings/Calls/CallsSettingsViewRouter.swift +++ b/iMEGA/My Account/Settings/Calls/CallsSettingsViewRouter.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation import MEGASDKRepo import SwiftUI @@ -11,7 +12,7 @@ final class CallsSettingsViewRouter: Routing { } func build() -> UIViewController { - let analyticsEventUseCase = AnalyticsEventUseCase(repository: AnalyticsRepository(sdk: MEGASdkManager.sharedMEGASdk())) + let analyticsEventUseCase = AnalyticsEventUseCase(repository: AnalyticsRepository(sdk: MEGASdk.shared)) let viewModel = CallsSettingsViewModel(analyticsEventUseCase: analyticsEventUseCase) let callsSettingsView = CallsSettingsView(viewModel: viewModel) let hostingController = UIHostingController(rootView: callsSettingsView) diff --git a/iMEGA/My Account/Settings/Calls/Views/CallsSettingsSoundNotificationsView.swift b/iMEGA/My Account/Settings/Calls/Views/CallsSettingsSoundNotificationsView.swift index e9ab16c78c..eac52c5441 100644 --- a/iMEGA/My Account/Settings/Calls/Views/CallsSettingsSoundNotificationsView.swift +++ b/iMEGA/My Account/Settings/Calls/Views/CallsSettingsSoundNotificationsView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct CallsSettingsSoundNotificationsView: View { diff --git a/iMEGA/My Account/Settings/Camera Uploads/CameraUploadAdvancedOptionsViewController.h b/iMEGA/My Account/Settings/Camera Uploads/CameraUploadAdvancedOptionsViewController.h index eee65be7fd..2034263ac4 100644 --- a/iMEGA/My Account/Settings/Camera Uploads/CameraUploadAdvancedOptionsViewController.h +++ b/iMEGA/My Account/Settings/Camera Uploads/CameraUploadAdvancedOptionsViewController.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/My Account/Settings/Camera Uploads/CameraUploadAdvancedOptionsViewController.m b/iMEGA/My Account/Settings/Camera Uploads/CameraUploadAdvancedOptionsViewController.m index 1ea87cd773..59513412cb 100644 --- a/iMEGA/My Account/Settings/Camera Uploads/CameraUploadAdvancedOptionsViewController.m +++ b/iMEGA/My Account/Settings/Camera Uploads/CameraUploadAdvancedOptionsViewController.m @@ -1,10 +1,11 @@ - #import "CameraUploadAdvancedOptionsViewController.h" #import "CameraUploadManager+Settings.h" #import "CameraScanner.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + typedef NS_ENUM(NSUInteger, AdvancedOptionSection) { AdvancedOptionSectionLivePhoto, AdvancedOptionSectionBurstPhoto, @@ -39,17 +40,17 @@ @implementation CameraUploadAdvancedOptionsViewController - (void)viewDidLoad { [super viewDidLoad]; - [self.navigationItem setTitle:NSLocalizedString(@"advanced", nil)]; + [self.navigationItem setTitle:LocalizedString(@"advanced", @"")]; - self.uploadVideosForLivePhotosLabel.text = NSLocalizedString(@"Upload Videos for Live Photos", @"Title of the switch to config whether to upload videos for Live Photos"); + self.uploadVideosForLivePhotosLabel.text = LocalizedString(@"Upload Videos for Live Photos", @"Title of the switch to config whether to upload videos for Live Photos"); self.uploadVideosForlivePhotosSwitch.on = CameraUploadManager.shouldUploadVideosForLivePhotos; - self.uploadAllBurstPhotosLabel.text = NSLocalizedString(@"Upload All Burst Photos", @"Title of the switch to config whether to upload all burst photos"); + self.uploadAllBurstPhotosLabel.text = LocalizedString(@"Upload All Burst Photos", @"Title of the switch to config whether to upload all burst photos"); self.uploadAllBurstPhotosSwitch.on = CameraUploadManager.shouldUploadAllBurstPhotos; - self.uploadHiddenAlbumLabel.text = NSLocalizedString(@"Upload Hidden Album", nil); + self.uploadHiddenAlbumLabel.text = LocalizedString(@"Upload Hidden Album", @""); self.uploadHiddenAlbumSwitch.on = CameraUploadManager.shouldUploadHiddenAlbum; - self.uploadSharedAlbumsLabel.text = NSLocalizedString(@"Upload Shared Albums", nil); + self.uploadSharedAlbumsLabel.text = LocalizedString(@"Upload Shared Albums", @""); self.uploadSharedAlbumsSwitch.on = CameraUploadManager.shouldUploadSharedAlbums; - self.uploadSyncedAlbumsLabel.text = NSLocalizedString(@"Upload Albums Synced from iTunes", @"Title of the switch to config whether to upload synced albums"); + self.uploadSyncedAlbumsLabel.text = LocalizedString(@"Upload Albums Synced from iTunes", @"Title of the switch to config whether to upload synced albums"); self.uploadSyncedAlbumsSwitch.on = CameraUploadManager.shouldUploadSyncedAlbums; self.tableView.backgroundColor = [UIColor mnz_backgroundGroupedForTraitCollection:self.traitCollection]; @@ -113,30 +114,30 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte switch (section) { case AdvancedOptionSectionLivePhoto: if (self.uploadVideosForlivePhotosSwitch.isOn) { - title = NSLocalizedString(@"The video and the photo in each Live Photo will be uploaded.", nil); + title = LocalizedString(@"The video and the photo in each Live Photo will be uploaded.", @""); } else { - title = NSLocalizedString(@"Only the photo in each Live Photo will be uploaded.", nil); + title = LocalizedString(@"Only the photo in each Live Photo will be uploaded.", @""); } break; case AdvancedOptionSectionBurstPhoto: if (self.uploadAllBurstPhotosSwitch.isOn) { - title = NSLocalizedString(@"All the photos from your burst photo sequences will be uploaded.", nil); + title = LocalizedString(@"All the photos from your burst photo sequences will be uploaded.", @""); } else { - title = NSLocalizedString(@"Only the representative photos from your burst photo sequences will be uploaded.", nil); + title = LocalizedString(@"Only the representative photos from your burst photo sequences will be uploaded.", @""); } break; case AdvancedOptionSectionHiddenAlbum: - title = NSLocalizedString(@"The Hidden Album is where you hide photos or videos in your device Photos app.", nil); + title = LocalizedString(@"The Hidden Album is where you hide photos or videos in your device Photos app.", @""); break; case AdvancedOptionSectionSharedAlbums: if (self.uploadSharedAlbumsSwitch.isOn) { - title = NSLocalizedString(@"Shared Albums from your device's Photos app will be uploaded.", nil); + title = LocalizedString(@"Shared Albums from your device's Photos app will be uploaded.", @""); } else { - title = NSLocalizedString(@"Shared Albums from your device's Photos app will not be uploaded.", nil); + title = LocalizedString(@"Shared Albums from your device's Photos app will not be uploaded.", @""); } break; case AdvancedOptionSectionSyncedAlbums: - title = NSLocalizedString(@"Synced albums are where you sync photos or videos to your device's Photos app from iTunes.", nil); + title = LocalizedString(@"Synced albums are where you sync photos or videos to your device's Photos app from iTunes.", @""); break; default: break; diff --git a/iMEGA/My Account/Settings/Camera Uploads/CameraUploadsTableViewController+Additions.swift b/iMEGA/My Account/Settings/Camera Uploads/CameraUploadsTableViewController+Additions.swift index 01bfbb4a29..a569517707 100644 --- a/iMEGA/My Account/Settings/Camera Uploads/CameraUploadsTableViewController+Additions.swift +++ b/iMEGA/My Account/Settings/Camera Uploads/CameraUploadsTableViewController+Additions.swift @@ -1,3 +1,4 @@ +import MEGAL10n extension CameraUploadsTableViewController { @objc func showAccountExpiredAlert() { diff --git a/iMEGA/My Account/Settings/Camera Uploads/CameraUploadsTableViewController.m b/iMEGA/My Account/Settings/Camera Uploads/CameraUploadsTableViewController.m index b218e76db1..56cd88ecbc 100644 --- a/iMEGA/My Account/Settings/Camera Uploads/CameraUploadsTableViewController.m +++ b/iMEGA/My Account/Settings/Camera Uploads/CameraUploadsTableViewController.m @@ -11,6 +11,8 @@ #import "MEGA-Swift.h" #import "TransferSessionManager.h" +@import MEGAL10nObjc; + @interface CameraUploadsTableViewController () @property (weak, nonatomic) IBOutlet UILabel *enableCameraUploadsLabel; @@ -64,17 +66,17 @@ - (void)viewDidLoad { [super viewDidLoad]; [self configureNavigationBar]; - [self.enableCameraUploadsLabel setText:NSLocalizedString(@"cameraUploadsLabel", nil)]; + [self.enableCameraUploadsLabel setText:LocalizedString(@"cameraUploadsLabel", @"")]; - [self.uploadVideosInfoLabel setText:NSLocalizedString(@"uploadVideosLabel", nil)]; - [self.uploadVideosLabel setText:NSLocalizedString(@"uploadVideosLabel", nil)]; + [self.uploadVideosInfoLabel setText:LocalizedString(@"uploadVideosLabel", @"")]; + [self.uploadVideosLabel setText:LocalizedString(@"uploadVideosLabel", @"")]; - self.useCellularConnectionLabel.text = NSLocalizedString(@"useMobileData", nil); - self.useCellularConnectionForVideosLabel.text = NSLocalizedString(@"Use Mobile Data for Videos", nil); + self.useCellularConnectionLabel.text = LocalizedString(@"useMobileData", @""); + self.useCellularConnectionForVideosLabel.text = LocalizedString(@"Use Mobile Data for Videos", @""); - self.advancedLabel.text = NSLocalizedString(@"advanced", nil); + self.advancedLabel.text = LocalizedString(@"advanced", @""); - self.includeGPSTagsLabel.text = NSLocalizedString(@"Include Location Tags", @"Used in camera upload settings: This text will appear with a switch to turn on/off location tags while uploading a file"); + self.includeGPSTagsLabel.text = LocalizedString(@"Include Location Tags", @"Used in camera upload settings: This text will appear with a switch to turn on/off location tags while uploading a file"); [self configImageFormatTexts]; @@ -115,7 +117,7 @@ - (void)configImageFormatTexts { NSMutableAttributedString *JPGAttributedString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ ", JPGFormat] attributes:formatAttributes]; - [JPGAttributedString appendAttributedString:[NSAttributedString.alloc initWithString:NSLocalizedString(@"(Recommended)", nil) attributes:@{NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : [UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection]}]]; + [JPGAttributedString appendAttributedString:[NSAttributedString.alloc initWithString:LocalizedString(@"(Recommended)", @"") attributes:@{NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : [UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection]}]]; self.JPGLabel.attributedText = JPGAttributedString; self.HEICLabel.attributedText = [[NSAttributedString alloc] initWithString:HEICFormat attributes:formatAttributes]; @@ -124,7 +126,7 @@ - (void)configImageFormatTexts { - (void)configUI { self.enableCameraUploadsSwitch.on = CameraUploadManager.isCameraUploadEnabled; self.uploadVideosSwitch.on = CameraUploadManager.isVideoUploadEnabled; - self.uploadVideosInfoRightDetailLabel.text = CameraUploadManager.isVideoUploadEnabled ? NSLocalizedString(@"on", nil) : NSLocalizedString(@"off", nil); + self.uploadVideosInfoRightDetailLabel.text = CameraUploadManager.isVideoUploadEnabled ? LocalizedString(@"on", @"") : LocalizedString(@"off", @""); self.includeGPSTagsSwitch.on = CameraUploadManager.includeGPSTags; [self configTargetFolder]; @@ -191,18 +193,18 @@ - (void)configTableSections { // photo format section [sections addObject:@[self.HEICCell, self.JPGCell]]; - [headerTitles addObject:NSLocalizedString(@"SAVE HEIC PHOTOS AS", @"What format to upload HEIC photos")]; - [footerTitles addObject:NSLocalizedString(@"We recommend JPG, as its the most compatible format for photos.", nil)]; + [headerTitles addObject:LocalizedString(@"SAVE HEIC PHOTOS AS", @"What format to upload HEIC photos")]; + [footerTitles addObject:LocalizedString(@"We recommend JPG, as its the most compatible format for photos.", @"")]; // Target folder [sections addObject:@[self.targetFolderCell]]; - [headerTitles addObject:NSLocalizedString(@"MEGA CAMERA UPLOADS FOLDER", nil)]; + [headerTitles addObject:LocalizedString(@"MEGA CAMERA UPLOADS FOLDER", @"")]; [footerTitles addObject:@""]; // Include GPS info cell. [sections addObject:@[self.includeGPSTagsCell]]; [headerTitles addObject:@""]; - [footerTitles addObject:NSLocalizedString(@"If enabled, you will upload information about where your pictures and videos were taken, so be careful when sharing them.", nil)]; + [footerTitles addObject:LocalizedString(@"If enabled, you will upload information about where your pictures and videos were taken, so be careful when sharing them.", @"")]; // options section NSMutableArray *optionSection = [NSMutableArray array]; @@ -214,7 +216,7 @@ - (void)configTableSections { } [optionSection addObjectsFromArray:@[self.advancedCell]]; [sections addObject:[optionSection copy]]; - [headerTitles addObject:NSLocalizedString(@"options", @"Camera Upload options")]; + [headerTitles addObject:LocalizedString(@"options", @"Camera Upload options")]; [footerTitles addObject:@""]; self.tableSections = [sections copy]; @@ -226,12 +228,12 @@ - (NSString *)titleForCameraUploadFooter { NSString *title; if (CameraUploadManager.isCameraUploadEnabled) { if (CameraUploadManager.isVideoUploadEnabled) { - title = NSLocalizedString(@"Photos and videos will be uploaded to Camera Uploads folder.", nil); + title = LocalizedString(@"Photos and videos will be uploaded to Camera Uploads folder.", @""); } else { - title = NSLocalizedString(@"Photos will be uploaded to Camera Uploads folder.", nil); + title = LocalizedString(@"Photos will be uploaded to Camera Uploads folder.", @""); } } else { - title = NSLocalizedString(@"When enabled, photos will be uploaded.", nil); + title = LocalizedString(@"When enabled, photos will be uploaded.", @""); } return title; @@ -257,9 +259,9 @@ - (IBAction)enableCameraUploadsSwitchValueChanged:(UISwitch *)sender { if (granted) { if ([MEGASdkManager.sharedMEGASdk isAccountType:MEGAAccountTypeBusiness] && !MEGASdkManager.sharedMEGASdk.isMasterBusinessAccount) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"cameraUploadsLabel", @"Title of one of the Settings sections where you can set up the 'Camera Uploads' options") message:NSLocalizedString(@"While MEGA does not have access to your data, your organization administrators do have the ability to control and view the Camera Uploads in your user account", @"Message shown when users with a business account (no administrators of a business account) try to enable the Camera Uploads, to advise them that the administrator do have the ability to view their data.") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"enable", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"cameraUploadsLabel", @"Title of one of the Settings sections where you can set up the 'Camera Uploads' options") message:LocalizedString(@"While MEGA does not have access to your data, your organization administrators do have the ability to control and view the Camera Uploads in your user account", @"Message shown when users with a business account (no administrators of a business account) try to enable the Camera Uploads, to advise them that the administrator do have the ability to view their data.") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"enable", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [CameraUploadManager.shared enableCameraUpload]; if (self.cameraUploadSettingChanged != nil) { self.cameraUploadSettingChanged(); diff --git a/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsQualityTableViewController.h b/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsQualityTableViewController.h index b06a897c51..51f84478a8 100644 --- a/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsQualityTableViewController.h +++ b/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsQualityTableViewController.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsQualityTableViewController.m b/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsQualityTableViewController.m index 157f96f4b0..1bd59f5fe9 100644 --- a/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsQualityTableViewController.m +++ b/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsQualityTableViewController.m @@ -1,4 +1,3 @@ - #import "VideoUploadsQualityTableViewController.h" #import "MEGA-Swift.h" @@ -6,6 +5,8 @@ #import "SelectableTableViewCell.h" #import "CameraUploadManager+Settings.h" +@import MEGAL10nObjc; + @interface VideoUploadsQualityTableViewController () @property (weak, nonatomic) IBOutlet UILabel *lowLabel; @@ -24,12 +25,12 @@ @implementation VideoUploadsQualityTableViewController - (void)viewDidLoad { [super viewDidLoad]; - self.navigationItem.title = NSLocalizedString(@"videoQuality", @"Title that refers to the quality of the chat (Either Online or Offline)"); + self.navigationItem.title = LocalizedString(@"videoQuality", @"Title that refers to the quality of the chat (Either Online or Offline)"); - self.lowLabel.text = NSLocalizedString(@"media.quality.low", @"Low"); - self.mediumLabel.text = NSLocalizedString(@"media.quality.medium", @"Medium"); - self.highLabel.text = NSLocalizedString(@"media.quality.high", @"High"); - self.originalLabel.text = NSLocalizedString(@"media.quality.original", @"Original"); + self.lowLabel.text = LocalizedString(@"media.quality.low", @"Low"); + self.mediumLabel.text = LocalizedString(@"media.quality.medium", @"Medium"); + self.highLabel.text = LocalizedString(@"media.quality.high", @"High"); + self.originalLabel.text = LocalizedString(@"media.quality.original", @"Original"); [self updateAppearance]; } diff --git a/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController+Additions.swift b/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController+Additions.swift index b3f4a7ba7f..50dedff4b6 100644 --- a/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController+Additions.swift +++ b/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController+Additions.swift @@ -1,3 +1,5 @@ +import MEGAL10n + extension VideoUploadsTableViewController { @objc func updateNavigationTitle() { let title = Strings.Localizable.CameraUploads.VideoUploads.title diff --git a/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController.h b/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController.h index 8b5f462eea..dbec026323 100644 --- a/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController.h +++ b/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController.m b/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController.m index a7d9ea51cf..3fcd5911d2 100644 --- a/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController.m +++ b/iMEGA/My Account/Settings/Camera Uploads/VideoUploadsTableViewController.m @@ -1,10 +1,9 @@ - #import "VideoUploadsTableViewController.h" - #import "MEGA-Swift.h" - #import "CameraUploadManager+Settings.h" +@import MEGAL10nObjc; + typedef NS_ENUM(NSUInteger, VideoUploadsSection) { VideoUploadsSectionFeatureSwitch, VideoUploadsSectionFormat, @@ -38,8 +37,8 @@ @implementation VideoUploadsTableViewController - (void)viewDidLoad { [super viewDidLoad]; - [self.uploadVideosLabel setText:NSLocalizedString(@"uploadVideosLabel", @"Title to switch on/off video uploads")]; - self.videoQualityLabel.text = NSLocalizedString(@"videoQuality", @"Title that refers to the video compression quality when to transcode from HEVC to H.264 codec"); + [self.uploadVideosLabel setText:LocalizedString(@"uploadVideosLabel", @"Title to switch on/off video uploads")]; + self.videoQualityLabel.text = LocalizedString(@"videoQuality", @"Title that refers to the video compression quality when to transcode from HEVC to H.264 codec"); [self configVideoFormatTexts]; [self updateNavigationTitle]; @@ -66,7 +65,7 @@ - (void)configVideoFormatTexts { NSDictionary *formatAttributes = @{NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : UIColor.mnz_label}; NSMutableAttributedString *H264AttributedString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ ", H264Format] attributes:formatAttributes]; - [H264AttributedString appendAttributedString:[NSAttributedString.alloc initWithString:NSLocalizedString(@"(Recommended)", nil) attributes:@{NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : [UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection]}]]; + [H264AttributedString appendAttributedString:[NSAttributedString.alloc initWithString:LocalizedString(@"(Recommended)", @"") attributes:@{NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody], NSForegroundColorAttributeName : [UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection]}]]; self.H264Label.attributedText = H264AttributedString; self.HEVCLabel.attributedText = [[NSAttributedString alloc] initWithString:HEVCFormat attributes:formatAttributes]; @@ -88,16 +87,16 @@ - (void)configVideoQualityUI { NSString *videoQualityString; switch (CameraUploadManager.HEVCToH264CompressionQuality) { case CameraUploadVideoQualityLow: - videoQualityString = NSLocalizedString(@"media.quality.low", @"Low"); + videoQualityString = LocalizedString(@"media.quality.low", @"Low"); break; case CameraUploadVideoQualityMedium: - videoQualityString = NSLocalizedString(@"media.quality.medium", @"Medium"); + videoQualityString = LocalizedString(@"media.quality.medium", @"Medium"); break; case CameraUploadVideoQualityHigh: - videoQualityString = NSLocalizedString(@"media.quality.high", @"High"); + videoQualityString = LocalizedString(@"media.quality.high", @"High"); break; case CameraUploadVideoQualityOriginal: - videoQualityString = NSLocalizedString(@"media.quality.original", @"Original"); + videoQualityString = LocalizedString(@"media.quality.original", @"Original"); break; default: break; @@ -176,7 +175,7 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte NSString *title; switch (section) { case VideoUploadsSectionFormat: - title = NSLocalizedString(@"SAVE HEVC VIDEOS AS", nil); + title = LocalizedString(@"SAVE HEVC VIDEOS AS", @""); break; default: break; @@ -190,16 +189,16 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte switch (section) { case VideoUploadsSectionFeatureSwitch: if (CameraUploadManager.isVideoUploadEnabled) { - title = NSLocalizedString(@"Videos will be uploaded to the Camera Uploads folder.", nil); + title = LocalizedString(@"Videos will be uploaded to the Camera Uploads folder.", @""); } else { - title = NSLocalizedString(@"When enabled, videos will be uploaded.", nil); + title = LocalizedString(@"When enabled, videos will be uploaded.", @""); } break; case VideoUploadsSectionFormat: - title = NSLocalizedString(@"We recommend H.264, as its the most compatible format for videos.", nil); + title = LocalizedString(@"We recommend H.264, as its the most compatible format for videos.", @""); break; case VideoUploadsSectionQuality: - title = NSLocalizedString(@"Compression quality when to transcode HEVC videos to H.264 format.", nil); + title = LocalizedString(@"Compression quality when to transcode HEVC videos to H.264 format.", @""); break; default: break; diff --git a/iMEGA/My Account/Settings/Chat/ChatImageQualityTableViewController.swift b/iMEGA/My Account/Settings/Chat/ChatImageQualityTableViewController.swift index 96c5400590..3a40215e67 100644 --- a/iMEGA/My Account/Settings/Chat/ChatImageQualityTableViewController.swift +++ b/iMEGA/My Account/Settings/Chat/ChatImageQualityTableViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit class ChatImageQualityTableViewController: UITableViewController { diff --git a/iMEGA/My Account/Settings/Chat/ChatSettingsTableViewController.m b/iMEGA/My Account/Settings/Chat/ChatSettingsTableViewController.m index 068209fd96..9680e4d320 100644 --- a/iMEGA/My Account/Settings/Chat/ChatSettingsTableViewController.m +++ b/iMEGA/My Account/Settings/Chat/ChatSettingsTableViewController.m @@ -15,6 +15,8 @@ #import "MEGA-Swift.h" #import "ChatImageUploadQuality.h" +@import MEGAL10nObjc; + typedef NS_ENUM(NSInteger, ChatSettingsSection) { ChatSettingsSectionStatus = 0, ChatSettingsSectionNotification, @@ -72,21 +74,21 @@ - (void)viewDidLoad { self.notificationSectionRows = @[@(ChatSettingsNotificationRowChatNotification), @(ChatSettingsNotificationRowDND)]; - NSString *title = NSLocalizedString(@"chat", @"Chat section header"); + NSString *title = LocalizedString(@"chat", @"Chat section header"); self.navigationItem.title = title; [self setMenuCapableBackButtonWithMenuTitle:title]; - self.statusLabel.text = NSLocalizedString(@"status", @"Title that refers to the status of the chat (Either Online or Offline)"); + self.statusLabel.text = LocalizedString(@"status", @"Title that refers to the status of the chat (Either Online or Offline)"); - self.richPreviewsLabel.text = NSLocalizedString(@"richUrlPreviews", @"Title used in settings that enables the generation of link previews in the chat"); + self.richPreviewsLabel.text = LocalizedString(@"richUrlPreviews", @"Title used in settings that enables the generation of link previews in the chat"); - self.imageQualityLabel.text = NSLocalizedString(@"Image Quality", @"Label used near to the option selected to encode the images uploaded to a chat (Automatic, High, Optimised)"); + self.imageQualityLabel.text = LocalizedString(@"Image Quality", @"Label used near to the option selected to encode the images uploaded to a chat (Automatic, High, Optimised)"); - self.videoQualityLabel.text = NSLocalizedString(@"videoQuality", @"Title that refers to the status of the chat (Either Online or Offline)"); + self.videoQualityLabel.text = LocalizedString(@"videoQuality", @"Title that refers to the status of the chat (Either Online or Offline)"); - self.doNotDisturbLabel.text = NSLocalizedString(@"Do Not Disturb", nil); + self.doNotDisturbLabel.text = LocalizedString(@"Do Not Disturb", @""); - self.chatNotificationsLabel.text = NSLocalizedString(@"Chat Notifications", @"Title that refers to disabling the chat notifications forever."); + self.chatNotificationsLabel.text = LocalizedString(@"Chat Notifications", @"Title that refers to disabling the chat notifications forever."); self.richPreviewsSwitch.on = [NSUserDefaults.standardUserDefaults boolForKey:@"richLinks"]; @@ -197,15 +199,15 @@ - (void)imageQualityString { NSString *imageQualityString; switch (imageQuality) { case ChatImageUploadQualityAuto: - imageQualityString = NSLocalizedString(@"media.quality.automatic", @"Indicating that the image quality will be determine by MEGA."); + imageQualityString = LocalizedString(@"media.quality.automatic", @"Indicating that the image quality will be determine by MEGA."); break; case ChatImageUploadQualityOriginal: - imageQualityString = NSLocalizedString(@"media.quality.original", @"Indicating that the image quality will be the same."); + imageQualityString = LocalizedString(@"media.quality.original", @"Indicating that the image quality will be the same."); break; case ChatImageUploadQualityOptimised: - imageQualityString = NSLocalizedString(@"media.quality.optimised", @"Indicating that the image will be optimised."); + imageQualityString = LocalizedString(@"media.quality.optimised", @"Indicating that the image will be optimised."); break; default: @@ -226,19 +228,19 @@ - (void)videoQualityString { switch (videoQuality) { case ChatVideoUploadQualityLow: - videoQualityString = NSLocalizedString(@"media.quality.low", @"Low"); + videoQualityString = LocalizedString(@"media.quality.low", @"Low"); break; case ChatVideoUploadQualityMedium: - videoQualityString = NSLocalizedString(@"media.quality.medium", @"Medium"); + videoQualityString = LocalizedString(@"media.quality.medium", @"Medium"); break; case ChatVideoUploadQualityHigh: - videoQualityString = NSLocalizedString(@"media.quality.high", @"High"); + videoQualityString = LocalizedString(@"media.quality.high", @"High"); break; case ChatVideoUploadQualityOriginal: - videoQualityString = NSLocalizedString(@"media.quality.original", @"Original"); + videoQualityString = LocalizedString(@"media.quality.original", @"Original"); break; default: @@ -273,7 +275,7 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte break; case ChatSettingsSectionRichPreview: - footerTitle = NSLocalizedString(@"richPreviewsFooter", @"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."); + footerTitle = LocalizedString(@"richPreviewsFooter", @"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."); break; case ChatSettingsSectionImageQuality: @@ -281,15 +283,15 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte ChatImageUploadQuality imageQuality = [NSUserDefaults.standardUserDefaults integerForKey:@"chatImageQuality"]; switch (imageQuality) { case ChatImageUploadQualityAuto: - footerTitle = NSLocalizedString(@"Send smaller size images through cellular networks and original size images through wifi", @"Description of Automatic Image Quality option"); + footerTitle = LocalizedString(@"Send smaller size images through cellular networks and original size images through wifi", @"Description of Automatic Image Quality option"); break; case ChatImageUploadQualityOriginal: - footerTitle = NSLocalizedString(@"Send original size, increased quality images", @"Description of Original Image Quality option"); + footerTitle = LocalizedString(@"Send original size, increased quality images", @"Description of Original Image Quality option"); break; case ChatImageUploadQualityOptimised: - footerTitle = NSLocalizedString(@"Send smaller size images optimised for lower data consumption", @"Description of Optimised Image Quality option"); + footerTitle = LocalizedString(@"Send smaller size images optimised for lower data consumption", @"Description of Optimised Image Quality option"); break; default: @@ -299,7 +301,7 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte break; case ChatSettingsSectionVideoQuality: - footerTitle = NSLocalizedString(@"qualityOfVideosUploadedToAChat", @"Footer text to explain the meaning of the functionaly 'Video quality' for videos uploaded to a chat."); + footerTitle = LocalizedString(@"qualityOfVideosUploadedToAChat", @"Footer text to explain the meaning of the functionaly 'Video quality' for videos uploaded to a chat."); break; default: diff --git a/iMEGA/My Account/Settings/Chat/ChatSettingsTableViewController.swift b/iMEGA/My Account/Settings/Chat/ChatSettingsTableViewController.swift index f87eeda67f..0e54848c1d 100644 --- a/iMEGA/My Account/Settings/Chat/ChatSettingsTableViewController.swift +++ b/iMEGA/My Account/Settings/Chat/ChatSettingsTableViewController.swift @@ -1,4 +1,3 @@ - extension ChatSettingsTableViewController: PushNotificationControlProtocol { func presentAlertController(_ alert: UIAlertController) { present(alert, animated: true) diff --git a/iMEGA/My Account/Settings/Chat/ChatStatusTableViewController+Additions.swift b/iMEGA/My Account/Settings/Chat/ChatStatusTableViewController+Additions.swift index 182a3cd2e2..068a46f0ea 100644 --- a/iMEGA/My Account/Settings/Chat/ChatStatusTableViewController+Additions.swift +++ b/iMEGA/My Account/Settings/Chat/ChatStatusTableViewController+Additions.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n import UIKit extension ChatStatusTableViewController { diff --git a/iMEGA/My Account/Settings/Chat/ChatStatusTableViewController.m b/iMEGA/My Account/Settings/Chat/ChatStatusTableViewController.m index 67149bdb9c..61e766f913 100644 --- a/iMEGA/My Account/Settings/Chat/ChatStatusTableViewController.m +++ b/iMEGA/My Account/Settings/Chat/ChatStatusTableViewController.m @@ -10,6 +10,8 @@ #import "MEGA-Swift.h" #import "NSArray+MNZCategory.h" +@import MEGAL10nObjc; + @interface ChatStatusTableViewController () @property (weak, nonatomic) IBOutlet UILabel *onlineLabel; @@ -42,19 +44,19 @@ - (void)viewDidLoad { self.tableView.emptyDataSetSource = self; self.tableView.emptyDataSetDelegate = self; - self.navigationItem.title = NSLocalizedString(@"status", @"Title that refers to the status of the chat (Either Online or Offline)"); + self.navigationItem.title = LocalizedString(@"status", @"Title that refers to the status of the chat (Either Online or Offline)"); [self.tableView registerNib:[UINib nibWithNibName:@"SelectableTableViewCell" bundle:nil] forCellReuseIdentifier:@"SelectableTableViewCellID"]; - self.onlineLabel.text = NSLocalizedString(@"online", nil); - self.awayLabel.text = NSLocalizedString(@"away", nil); - self.busyLabel.text = NSLocalizedString(@"busy", nil); - self.offlineLabel.text = NSLocalizedString(@"offline", @"Title of the Offline section"); + self.onlineLabel.text = LocalizedString(@"online", @""); + self.awayLabel.text = LocalizedString(@"away", @""); + self.busyLabel.text = LocalizedString(@"busy", @""); + self.offlineLabel.text = LocalizedString(@"offline", @"Title of the Offline section"); - self.autoAwayLabel.text = NSLocalizedString(@"autoAway", nil); + self.autoAwayLabel.text = LocalizedString(@"autoAway", @""); - self.statusPersistenceLabel.text = NSLocalizedString(@"statusPersistence", nil); - [self.autoAwayTimeSaveButton setTitle:NSLocalizedString(@"save", @"Button title to 'Save' the selected option") forState:UIControlStateNormal]; + self.statusPersistenceLabel.text = LocalizedString(@"statusPersistence", @""); + [self.autoAwayTimeSaveButton setTitle:LocalizedString(@"save", @"Button title to 'Save' the selected option") forState:UIControlStateNormal]; [self setLastActiveLabelAttributedText]; @@ -131,8 +133,8 @@ - (void)updateUIWithPresenceConfig { - (void)setLastActiveLabelAttributedText { UIFont *font = [UIFont mnz_preferredFontWithStyle:UIFontTextStyleBody weight:UIFontWeightRegular].italic; - NSAttributedString *lastSeenString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:NSLocalizedString(@"Last seen %s", nil), "..."] attributes:@{NSFontAttributeName: font}]; - NSMutableAttributedString *showLastSeenAttributedString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ ", NSLocalizedString(@"Show", @"Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...'")]]; + NSAttributedString *lastSeenString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:LocalizedString(@"Last seen %s", @""), "..."] attributes:@{NSFontAttributeName: font}]; + NSMutableAttributedString *showLastSeenAttributedString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ ", LocalizedString(@"Show", @"Label shown next to a feature name that can be enabled or disabled, like in 'Show Last seen...'")]]; [showLastSeenAttributedString appendAttributedString:lastSeenString]; self.lastActiveLabel.attributedText = showLastSeenAttributedString; @@ -268,16 +270,16 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte break; case 1: - titleForFooter = NSLocalizedString(@"Allow your contacts to see the last time you were active on MEGA.", @"Footer text to explain the meaning of the functionaly 'Last seen' of your chat status."); + titleForFooter = LocalizedString(@"Allow your contacts to see the last time you were active on MEGA.", @"Footer text to explain the meaning of the functionaly 'Last seen' of your chat status."); break; case 2: - titleForFooter = NSLocalizedString(@"maintainMyChosenStatusAppearance", @"Footer text to explain the meaning of the functionaly 'Auto-away' of your chat status."); + titleForFooter = LocalizedString(@"maintainMyChosenStatusAppearance", @"Footer text to explain the meaning of the functionaly 'Auto-away' of your chat status."); break; case 3: if (self.presenceConfig.isAutoAwayEnabled && !self.isSelectingTimeout) { - titleForFooter = NSLocalizedString(@"autoAway.footerDescription", @"Footer text to explain the meaning of the functionaly Auto-away of your chat status."); + titleForFooter = LocalizedString(@"autoAway.footerDescription", @"Footer text to explain the meaning of the functionaly Auto-away of your chat status."); titleForFooter = [titleForFooter stringByReplacingOccurrencesOfString:@"[X]" withString:[self formatHoursAndMinutes]]; } break; @@ -357,7 +359,7 @@ - (nullable UIView *)customViewForEmptyDataSet:(UIScrollView *)scrollView { - (NSString *)titleForEmptyState { NSString *text = @""; if (![MEGAReachabilityManager isReachable]) { - text = NSLocalizedString(@"noInternetConnection", @"Text shown on the app when you don't have connection to the internet or when you have lost it"); + text = LocalizedString(@"noInternetConnection", @"Text shown on the app when you don't have connection to the internet or when you have lost it"); } return text; @@ -366,7 +368,7 @@ - (NSString *)titleForEmptyState { - (NSString *)descriptionForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); + text = LocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); } return text; @@ -383,7 +385,7 @@ - (UIImage *)imageForEmptyState { - (NSString *)buttonTitleForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); + text = LocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); } return text; diff --git a/iMEGA/My Account/Settings/Chat/ChatVideoQualityTableViewController.h b/iMEGA/My Account/Settings/Chat/ChatVideoQualityTableViewController.h index 9cb4318e3e..b239e49715 100644 --- a/iMEGA/My Account/Settings/Chat/ChatVideoQualityTableViewController.h +++ b/iMEGA/My Account/Settings/Chat/ChatVideoQualityTableViewController.h @@ -1,4 +1,3 @@ - #import @interface ChatVideoQualityTableViewController : UITableViewController diff --git a/iMEGA/My Account/Settings/Chat/ChatVideoQualityTableViewController.m b/iMEGA/My Account/Settings/Chat/ChatVideoQualityTableViewController.m index 434f973e39..2df2b3cd76 100644 --- a/iMEGA/My Account/Settings/Chat/ChatVideoQualityTableViewController.m +++ b/iMEGA/My Account/Settings/Chat/ChatVideoQualityTableViewController.m @@ -1,10 +1,11 @@ - #import "ChatVideoQualityTableViewController.h" #import "MEGA-Swift.h" #import "ChatVideoUploadQuality.h" +@import MEGAL10nObjc; + @interface ChatVideoQualityTableViewController () @property (weak, nonatomic) IBOutlet UILabel *lowLabel; @@ -23,12 +24,12 @@ @implementation ChatVideoQualityTableViewController - (void)viewDidLoad { [super viewDidLoad]; - self.navigationItem.title = NSLocalizedString(@"videoQuality", @"Title that refers to the quality of the chat (Either Online or Offline)"); + self.navigationItem.title = LocalizedString(@"videoQuality", @"Title that refers to the quality of the chat (Either Online or Offline)"); - _lowLabel.text = NSLocalizedString(@"media.quality.low", @"Low"); - _mediumLabel.text = NSLocalizedString(@"media.quality.medium", @"Medium"); - _highLabel.text = NSLocalizedString(@"media.quality.high", @"High"); - _originalLabel.text = NSLocalizedString(@"media.quality.original", @"Original"); + _lowLabel.text = LocalizedString(@"media.quality.low", @"Low"); + _mediumLabel.text = LocalizedString(@"media.quality.medium", @"Medium"); + _highLabel.text = LocalizedString(@"media.quality.high", @"High"); + _originalLabel.text = LocalizedString(@"media.quality.original", @"Original"); [self updateAppearance]; } diff --git a/iMEGA/My Account/Settings/Chat/DND/DNDTurnOnOption.swift b/iMEGA/My Account/Settings/Chat/DND/DNDTurnOnOption.swift index 403b7d1f2e..14cdaf6a17 100644 --- a/iMEGA/My Account/Settings/Chat/DND/DNDTurnOnOption.swift +++ b/iMEGA/My Account/Settings/Chat/DND/DNDTurnOnOption.swift @@ -1,6 +1,7 @@ import Foundation import MEGADomain import MEGAFoundation +import MEGAL10n enum DNDTurnOnOption: String, CaseIterable { case thirtyMinutes diff --git a/iMEGA/My Account/Settings/Chat/DND/PushNotificationControl.swift b/iMEGA/My Account/Settings/Chat/DND/PushNotificationControl.swift index 8bb4108649..678e53a619 100644 --- a/iMEGA/My Account/Settings/Chat/DND/PushNotificationControl.swift +++ b/iMEGA/My Account/Settings/Chat/DND/PushNotificationControl.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import UIKit /// A protocol to manage PushNotifications based on user preferences @@ -38,12 +39,12 @@ class PushNotificationControl: NSObject, MEGARequestDelegate { @objc init(delegate: any PushNotificationControlProtocol) { self.delegate = delegate super.init() - MEGASdkManager.sharedMEGASdk().add(self as (any MEGARequestDelegate)) - MEGASdkManager.sharedMEGASdk().getPushNotificationSettings() + MEGASdk.shared.add(self as (any MEGARequestDelegate)) + MEGASdk.shared.getPushNotificationSettings() } deinit { - MEGASdkManager.sharedMEGASdk().remove(self as (any MEGARequestDelegate)) + MEGASdk.shared.remove(self as (any MEGARequestDelegate)) } // MARK: - Interface. @@ -101,7 +102,7 @@ extension PushNotificationControl { showProgress() block() - MEGASdkManager.sharedMEGASdk().setPushNotificationSettings(pushNotificationSettings) + MEGASdk.shared.setPushNotificationSettings(pushNotificationSettings) } func dndTimeInterval(dndTurnOnOption: DNDTurnOnOption) -> Int64? { diff --git a/iMEGA/My Account/Settings/CookiesSettingsScene/CookieSettingsTableViewController.swift b/iMEGA/My Account/Settings/CookiesSettingsScene/CookieSettingsTableViewController.swift index ed8f25dcf1..b84c32f16c 100644 --- a/iMEGA/My Account/Settings/CookiesSettingsScene/CookieSettingsTableViewController.swift +++ b/iMEGA/My Account/Settings/CookiesSettingsScene/CookieSettingsTableViewController.swift @@ -1,5 +1,5 @@ - import Foundation +import MEGAL10n enum CookieSettingsSection: Int { case acceptCookies diff --git a/iMEGA/My Account/Settings/CookiesSettingsScene/CookieSettingsViewModel.swift b/iMEGA/My Account/Settings/CookiesSettingsScene/CookieSettingsViewModel.swift index 13fdec9ffb..9743eb4009 100644 --- a/iMEGA/My Account/Settings/CookiesSettingsScene/CookieSettingsViewModel.swift +++ b/iMEGA/My Account/Settings/CookiesSettingsScene/CookieSettingsViewModel.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n import MEGAPresentation enum CookiesBitPosition: Int { diff --git a/iMEGA/My Account/Settings/Delete Account/DeleteAccountRouter.swift b/iMEGA/My Account/Settings/Delete Account/DeleteAccountRouter.swift index d8e23133e8..4ab3124b79 100644 --- a/iMEGA/My Account/Settings/Delete Account/DeleteAccountRouter.swift +++ b/iMEGA/My Account/Settings/Delete Account/DeleteAccountRouter.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGAPresentation import UIKit @@ -51,13 +52,13 @@ class DeleteAccountRouter: Routing { private func deleteActionForTwoFactorAuthDisabled() { guard let settingsVc = presenter as? SettingsTableViewController else { return } - MEGASdkManager.sharedMEGASdk().cancelAccount(with: settingsVc) + MEGASdk.shared.cancelAccount(with: settingsVc) } private func getMultiFactorAuthenticationStatus(completion: @escaping (Bool) -> Void) { guard MEGAReachabilityManager.isReachable() else { return } guard let myEmail = MEGASdk.currentUserEmail else { return } - MEGASdkManager.sharedMEGASdk() + MEGASdk.shared .multiFactorAuthCheck(withEmail: myEmail, delegate: MEGAMultiFactorAuthCheckRequestDelegate { (request, _) in guard let authRequest = request else { return } diff --git a/iMEGA/My Account/Settings/File Management/File versioning/FileVersioningTableViewController.swift b/iMEGA/My Account/Settings/File Management/File versioning/FileVersioningTableViewController.swift index fe1c11abe2..f140a8897a 100644 --- a/iMEGA/My Account/Settings/File Management/File versioning/FileVersioningTableViewController.swift +++ b/iMEGA/My Account/Settings/File Management/File versioning/FileVersioningTableViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGAPresentation import MEGAUIKit import UIKit diff --git a/iMEGA/My Account/Settings/File Management/File versioning/FileVersioningViewRouter.swift b/iMEGA/My Account/Settings/File Management/File versioning/FileVersioningViewRouter.swift index e0b48d38db..dcb6792fba 100644 --- a/iMEGA/My Account/Settings/File Management/File versioning/FileVersioningViewRouter.swift +++ b/iMEGA/My Account/Settings/File Management/File versioning/FileVersioningViewRouter.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n final class FileVersioningViewRouter: NSObject, FileVersioningViewRouting { private weak var baseViewController: UIViewController? @@ -9,7 +10,7 @@ final class FileVersioningViewRouter: NSObject, FileVersioningViewRouting { } func build() -> UIViewController { - let sdk = MEGASdkManager.sharedMEGASdk() + let sdk = MEGASdk.shared let repo = FileVersionsRepository(sdk: sdk) let useCase = FileVersionsUseCase(repo: repo) let accountRepo = AccountRepository(sdk: sdk) diff --git a/iMEGA/My Account/Settings/File Management/FileManagementTableViewController.m b/iMEGA/My Account/Settings/File Management/FileManagementTableViewController.m index 8a0905f6ab..de2d7468fe 100644 --- a/iMEGA/My Account/Settings/File Management/FileManagementTableViewController.m +++ b/iMEGA/My Account/Settings/File Management/FileManagementTableViewController.m @@ -1,4 +1,3 @@ - #import "FileManagementTableViewController.h" #import "SVProgressHUD.h" @@ -11,6 +10,8 @@ #import "MEGA-Swift.h" #import "NSFileManager+MNZCategory.h" #import "NSString+MNZCategory.h" + +@import MEGAL10nObjc; @import MEGASDKRepo; typedef NS_ENUM(NSUInteger, FileManagementTableSection) { @@ -48,7 +49,7 @@ @implementation FileManagementTableViewController - (void)viewDidLoad { [super viewDidLoad]; - NSString *title = NSLocalizedString(@"File Management", @"A section header which contains the file management settings. These settings allow users to remove duplicate files etc."); + NSString *title = LocalizedString(@"File Management", @"A section header which contains the file management settings. These settings allow users to remove duplicate files etc."); self.navigationItem.title = title; [self setMenuCapableBackButtonWithMenuTitle: title]; @@ -61,15 +62,15 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - self.clearOfflineFilesLabel.text = NSLocalizedString(@"clearOfflineFiles", @"Section title where you can 'Clear Offline files' of your MEGA app"); - self.clearCacheLabel.text = NSLocalizedString(@"clearCache", @"Section title where you can 'Clear Cache' of your MEGA app"); + self.clearOfflineFilesLabel.text = LocalizedString(@"clearOfflineFiles", @"Section title where you can 'Clear Offline files' of your MEGA app"); + self.clearCacheLabel.text = LocalizedString(@"clearCache", @"Section title where you can 'Clear Cache' of your MEGA app"); - self.rubbishBinLabel.text = NSLocalizedString(@"rubbishBinLabel", @"Title of one of the Settings sections where you can see your MEGA 'Rubbish Bin'"); + self.rubbishBinLabel.text = LocalizedString(@"rubbishBinLabel", @"Title of one of the Settings sections where you can see your MEGA 'Rubbish Bin'"); - self.fileVersioningLabel.text = NSLocalizedString(@"File versioning", @"Title of the option to enable or disable file versioning on Settings section"); + self.fileVersioningLabel.text = LocalizedString(@"File versioning", @"Title of the option to enable or disable file versioning on Settings section"); [[MEGASdkManager sharedMEGASdk] getFileVersionsOptionWithDelegate:self]; - self.useMobileDataLabel.text = NSLocalizedString(@"useMobileData", @"Title next to a switch button (On-Off) to allow using mobile data (Roaming) for a feature."); + self.useMobileDataLabel.text = LocalizedString(@"useMobileData", @"Title next to a switch button (On-Off) to allow using mobile data (Roaming) for a feature."); self.useMobileDataSwitch.on = [NSUserDefaults.standardUserDefaults boolForKey:MEGAUseMobileDataForPreviewingOriginalPhoto]; [[MEGASdkManager sharedMEGASdk] addMEGAGlobalDelegate:self]; @@ -141,10 +142,10 @@ - (void)removeGroupSharedDirectoryContents { } - (void)showClearAllOfflineFilesActionSheet:(UIView *)sender { - UIAlertController *clearAllOfflineFilesAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"settings.fileManagement.alert.clearAllOfflineFiles", @"Question shown after you tap on 'Settings' - 'File Management' - 'Clear Offline files' to confirm the action") message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - [clearAllOfflineFilesAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *clearAllOfflineFilesAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"settings.fileManagement.alert.clearAllOfflineFiles", @"Question shown after you tap on 'Settings' - 'File Management' - 'Clear Offline files' to confirm the action") message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + [clearAllOfflineFilesAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - UIAlertAction *clearAlertAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"clear", @"Button title to clear something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertAction *clearAlertAction = [UIAlertAction actionWithTitle:LocalizedString(@"clear", @"Button title to clear something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [self clearOfflineFiles]; }]; [clearAllOfflineFilesAlertController addAction:clearAlertAction]; @@ -183,14 +184,14 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte NSString *titleHeader; switch (section) { case FileManagementTableSectionMobileData: - titleHeader = NSLocalizedString(@"settings.fileManagement.useMobileData.header", @"Header of a Use Mobile Data setting to load preview of images in hight resolution"); + titleHeader = LocalizedString(@"settings.fileManagement.useMobileData.header", @"Header of a Use Mobile Data setting to load preview of images in hight resolution"); break; case FileManagementTableSectionOnYourDevice: - titleHeader = NSLocalizedString(@"onYourDevice", @"Title header that refers to where do you do the actions 'Clear Offlines files' and 'Clear cache' inside 'Settings' -> 'Advanced' section"); + titleHeader = LocalizedString(@"onYourDevice", @"Title header that refers to where do you do the actions 'Clear Offlines files' and 'Clear cache' inside 'Settings' -> 'Advanced' section"); break; case FileManagementTableSectionOnMEGA: - titleHeader = NSLocalizedString(@"onMEGA", @"Title header that refers to where do you do the action 'Empty Rubbish Bin' inside 'Settings' -> 'Advanced' section"); + titleHeader = LocalizedString(@"onMEGA", @"Title header that refers to where do you do the action 'Empty Rubbish Bin' inside 'Settings' -> 'Advanced' section"); break; } @@ -201,18 +202,18 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte NSString *titleFooter; switch (section) { case FileManagementTableSectionMobileData: { - titleFooter = NSLocalizedString(@"settings.fileManagement.useMobileData.footer", @"Footer explaning how Use Mobile Data setting to load preview of images in hight resolution works"); + titleFooter = LocalizedString(@"settings.fileManagement.useMobileData.footer", @"Footer explaning how Use Mobile Data setting to load preview of images in hight resolution works"); break; } case FileManagementTableSectionOnYourDevice: { - NSString *currentlyUsingString = NSLocalizedString(@"currentlyUsing", @"Footer text that explain what amount of space you will free up if 'Clear Offline data', 'Clear cache' or 'Clear Rubbish Bin' is tapped"); + NSString *currentlyUsingString = LocalizedString(@"currentlyUsing", @"Footer text that explain what amount of space you will free up if 'Clear Offline data', 'Clear cache' or 'Clear Rubbish Bin' is tapped"); currentlyUsingString = [currentlyUsingString stringByReplacingOccurrencesOfString:@"%s" withString:self.offlineSizeString]; titleFooter = currentlyUsingString; break; } case FileManagementTableSectionClearCache: { - NSString *currentlyUsingString = NSLocalizedString(@"currentlyUsing", @"Footer text that explain what amount of space you will free up if 'Clear Offline data', 'Clear cache' or 'Clear Rubbish Bin' is tapped"); + NSString *currentlyUsingString = LocalizedString(@"currentlyUsing", @"Footer text that explain what amount of space you will free up if 'Clear Offline data', 'Clear cache' or 'Clear Rubbish Bin' is tapped"); currentlyUsingString = [currentlyUsingString stringByReplacingOccurrencesOfString:@"%s" withString:self.cacheSizeString]; titleFooter = currentlyUsingString; break; @@ -222,7 +223,7 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte NSNumber *rubbishBinSizeNumber = [[MEGASdkManager sharedMEGASdk] sizeForNode:[[MEGASdkManager sharedMEGASdk] rubbishNode]]; NSString *stringFromByteCount = [NSString memoryStyleStringFromByteCount:rubbishBinSizeNumber.unsignedLongLongValue]; stringFromByteCount = [NSString mnz_formatStringFromByteCountFormatter:stringFromByteCount]; - NSString *currentlyUsingString = NSLocalizedString(@"currentlyUsing", @"Footer text that explain what amount of space you will free up if 'Clear Offline data', 'Clear cache' or 'Clear Rubbish Bin' is tapped"); + NSString *currentlyUsingString = LocalizedString(@"currentlyUsing", @"Footer text that explain what amount of space you will free up if 'Clear Offline data', 'Clear cache' or 'Clear Rubbish Bin' is tapped"); currentlyUsingString = [currentlyUsingString stringByReplacingOccurrencesOfString:@"%s" withString:stringFromByteCount]; titleFooter = currentlyUsingString; break; @@ -312,7 +313,7 @@ - (void)onUsersUpdate:(MEGASdk *)api userList:(MEGAUserList *)userList { - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if ((request.type == MEGARequestTypeGetAttrUser) && (request.paramType == MEGAUserAttributeDisableVersions)) { if (!error.type || error.type == MEGAErrorTypeApiENoent) { - self.fileVersioningDetail.text = !request.flag ? NSLocalizedString(@"on", nil) : NSLocalizedString(@"off", nil); + self.fileVersioningDetail.text = !request.flag ? LocalizedString(@"on", @"") : LocalizedString(@"off", @""); } } } diff --git a/iMEGA/My Account/Settings/File Management/RubbishBinTableViewController.m b/iMEGA/My Account/Settings/File Management/RubbishBinTableViewController.m index cda80d11e4..cd33e0b825 100644 --- a/iMEGA/My Account/Settings/File Management/RubbishBinTableViewController.m +++ b/iMEGA/My Account/Settings/File Management/RubbishBinTableViewController.m @@ -1,4 +1,3 @@ - #import "RubbishBinTableViewController.h" #import "NSString+MNZCategory.h" @@ -14,6 +13,7 @@ #import "MEGASdk+MNZCategory.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; @import MEGAUIKit; @interface RubbishBinTableViewController () @@ -56,15 +56,15 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - self.navigationItem.title = NSLocalizedString(@"rubbishBinLabel", @"Title of one of the Settings sections where you can see your MEGA 'Rubbish Bin'"); + self.navigationItem.title = LocalizedString(@"rubbishBinLabel", @"Title of one of the Settings sections where you can see your MEGA 'Rubbish Bin'"); - self.clearRubbishBinLabel.text = NSLocalizedString(@"emptyRubbishBin", @"Section title where you can 'Empty Rubbish Bin' of your MEGA account"); + self.clearRubbishBinLabel.text = LocalizedString(@"emptyRubbishBin", @"Section title where you can 'Empty Rubbish Bin' of your MEGA account"); [self updateClearRubbishBinDetailLabel]; - self.rubbishBinCleaningSchedulerLabel.text = [NSLocalizedString(@"Rubbish-Bin Cleaning Scheduler:", @"Title for the Rubbish-Bin Cleaning Scheduler feature") stringByReplacingOccurrencesOfString:@":" withString:@""]; + self.rubbishBinCleaningSchedulerLabel.text = [LocalizedString(@"Rubbish-Bin Cleaning Scheduler:", @"Title for the Rubbish-Bin Cleaning Scheduler feature") stringByReplacingOccurrencesOfString:@":" withString:@""]; [self.rubbishBinCleaningSchedulerSwitch setOn:[[MEGASdkManager sharedMEGASdk] serverSideRubbishBinAutopurgeEnabled]]; - self.removeFilesOlderThanLabel.text = NSLocalizedString(@"Remove files older than", @"A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days."); + self.removeFilesOlderThanLabel.text = LocalizedString(@"Remove files older than", @"A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days."); [[MEGASdkManager sharedMEGASdk] getRubbishBinAutopurgePeriodWithDelegate:self]; } @@ -103,7 +103,7 @@ - (void)updateAppearance { } - (void)setLongerRetentionPeriodUpgradetoProLabel { - NSString *longerRetentionUpgradeToProText = NSLocalizedString(@"settings.fileManagement.rubbishBin.longerRetentionUpgrade", @"This text is displayed in Settings, File Management in Rubbish Bien view. Upgrade to Pro will be bold and green. And if you tap it, the Upgrade Account view will appear."); + NSString *longerRetentionUpgradeToProText = LocalizedString(@"settings.fileManagement.rubbishBin.longerRetentionUpgrade", @"This text is displayed in Settings, File Management in Rubbish Bien view. Upgrade to Pro will be bold and green. And if you tap it, the Upgrade Account view will appear."); NSString *semiboldAndGreenText = [longerRetentionUpgradeToProText mnz_stringBetweenString:@"[S]" andString:@"[/S]"]; longerRetentionUpgradeToProText = longerRetentionUpgradeToProText.mnz_removeWebclientFormatters; @@ -148,10 +148,10 @@ - (IBAction)scheduleRubbishBinClearingSwitchTouchUpInside:(UIButton *)sender { } else { CustomModalAlertViewController *customModalAlertVC = [[CustomModalAlertViewController alloc] init]; customModalAlertVC.image = [UIImage imageNamed:@"retention_illustration"]; - customModalAlertVC.viewTitle = [NSLocalizedString(@"Rubbish-Bin Cleaning Scheduler:", @"Title for the Rubbish-Bin Cleaning Scheduler feature") stringByReplacingOccurrencesOfString:@":" withString:@""]; - customModalAlertVC.detail = NSLocalizedString(@"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan.", @"Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user"); - customModalAlertVC.firstButtonTitle = NSLocalizedString(@"seePlans", @"Button title to see the available pro plans in MEGA"); - customModalAlertVC.dismissButtonTitle = NSLocalizedString(@"notNow", @"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."); + customModalAlertVC.viewTitle = [LocalizedString(@"Rubbish-Bin Cleaning Scheduler:", @"Title for the Rubbish-Bin Cleaning Scheduler feature") stringByReplacingOccurrencesOfString:@":" withString:@""]; + customModalAlertVC.detail = LocalizedString(@"To disable the Rubbish-Bin Cleaning Scheduler or set a longer retention period, you need to subscribe to a PRO plan.", @"Description shown when you try to disable the feature Rubbish-Bin Cleaning Scheduler and you are a free user"); + customModalAlertVC.firstButtonTitle = LocalizedString(@"seePlans", @"Button title to see the available pro plans in MEGA"); + customModalAlertVC.dismissButtonTitle = LocalizedString(@"notNow", @"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."); __weak typeof(CustomModalAlertViewController) *weakCustom = customModalAlertVC; customModalAlertVC.firstCompletion = ^{ [weakCustom dismissViewControllerAnimated:YES completion:^{ @@ -183,11 +183,11 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte NSString *titleFooter; switch (section) { case 0: - titleFooter = NSLocalizedString(@"emptyRubbishBinAlertTitle", @"Alert title shown when you tap 'Empty Rubbish Bin'"); + titleFooter = LocalizedString(@"emptyRubbishBinAlertTitle", @"Alert title shown when you tap 'Empty Rubbish Bin'"); break; case 1: - titleFooter = ([[MEGASdkManager sharedMEGASdk] mnz_isProAccount]) ? NSLocalizedString(@"The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days.", @"New server-side rubbish-bin cleaning scheduler description (for PRO users)") : NSLocalizedString(@"The Rubbish Bin is cleaned for you automatically. The minimum period is 7 days and your maximum period is 30 days.", @"New server-side rubbish-bin cleaning scheduler description (for Free users)"); + titleFooter = ([[MEGASdkManager sharedMEGASdk] mnz_isProAccount]) ? LocalizedString(@"The Rubbish Bin can be cleaned for you automatically. The minimum period is 7 days.", @"New server-side rubbish-bin cleaning scheduler description (for PRO users)") : LocalizedString(@"The Rubbish Bin is cleaned for you automatically. The minimum period is 7 days and your maximum period is 30 days.", @"New server-side rubbish-bin cleaning scheduler description (for Free users)"); break; } @@ -204,9 +204,9 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath switch (indexPath.section) { case 0: { //Clear Rubbish Bin if ([MEGAReachabilityManager isReachableHUDIfNot]) { - UIAlertController *emptyRubbishBinAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"emptyRubbishBinAlertTitle", @"Alert title shown when you tap 'Empty Rubbish Bin'") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [emptyRubbishBinAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [emptyRubbishBinAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *emptyRubbishBinAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"emptyRubbishBinAlertTitle", @"Alert title shown when you tap 'Empty Rubbish Bin'") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [emptyRubbishBinAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; + [emptyRubbishBinAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { self.clearRubbishBinDetailLabel.hidden = YES; [self.clearRubbishBinAI startAnimating]; [[MEGASdkManager sharedMEGASdk] cleanRubbishBinWithDelegate:[MEGAGenericRequestDelegate.alloc initWithCompletion:^(MEGARequest * _Nonnull request, MEGAError * _Nonnull error) { @@ -227,18 +227,18 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath case 1: { //Remove files older than if (indexPath.row == 1) { if ([MEGAReachabilityManager isReachableHUDIfNot]) { - UIAlertController *scheduleRubbishBinClearingAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Remove files older than", @"A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days.") message:nil preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *scheduleRubbishBinClearingAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"Remove files older than", @"A rubbish bin scheduler setting which allows removing old files from the rubbish bin automatically. E.g. Remove files older than 15 days.") message:nil preferredStyle:UIAlertControllerStyleAlert]; [scheduleRubbishBinClearingAlertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { textField.keyboardType = UIKeyboardTypeNumberPad; - textField.placeholder = [NSString stringWithFormat:NSLocalizedString(@"settings.fileManagement.rubbishBin.cleanScheduler.placeholder.days", @"Rubbish bin items to be removed in days placeholder")]; + textField.placeholder = LocalizedString(@"settings.fileManagement.rubbishBin.cleanScheduler.placeholder.days", @"Rubbish bin items to be removed in days placeholder"); [textField addTarget:self action:@selector(scheduleRubbishBinClearingTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { return textField.text.mnz_isDecimalNumber; }; }]; - [scheduleRubbishBinClearingAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - UIAlertAction *doneAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"done", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [scheduleRubbishBinClearingAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; + UIAlertAction *doneAction = [UIAlertAction actionWithTitle:LocalizedString(@"done", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { NSString *days = scheduleRubbishBinClearingAlertController.textFields.firstObject.text; if ([[MEGASdkManager sharedMEGASdk] mnz_isProAccount]) { if (days.integerValue > 365) { diff --git a/iMEGA/My Account/Settings/Help/HelpTableViewController.h b/iMEGA/My Account/Settings/Help/HelpTableViewController.h index cfe36b44a5..a5bc8c6f56 100644 --- a/iMEGA/My Account/Settings/Help/HelpTableViewController.h +++ b/iMEGA/My Account/Settings/Help/HelpTableViewController.h @@ -1,4 +1,3 @@ - #import @class SendFeedbackViewModel; diff --git a/iMEGA/My Account/Settings/Help/HelpTableViewController.m b/iMEGA/My Account/Settings/Help/HelpTableViewController.m index d517d8edc3..b9c6949457 100644 --- a/iMEGA/My Account/Settings/Help/HelpTableViewController.m +++ b/iMEGA/My Account/Settings/Help/HelpTableViewController.m @@ -1,4 +1,3 @@ - #import "HelpTableViewController.h" #import @@ -11,6 +10,8 @@ #import "MEGA-Swift.h" #import "NSURL+MNZCategory.h" +@import MEGAL10nObjc; + @interface HelpTableViewController () @property (weak, nonatomic) IBOutlet UILabel *helpCentreLabel; @@ -28,15 +29,15 @@ @implementation HelpTableViewController - (void)viewDidLoad { [super viewDidLoad]; - self.navigationItem.title = NSLocalizedString(@"help", @"Menu item"); + self.navigationItem.title = LocalizedString(@"help", @"Menu item"); - self.sendFeedbackLabel.text = NSLocalizedString(@"sendFeedbackLabel", @"Title of one of the Settings sections where you can 'Send Feedback' to MEGA"); - self.helpCentreLabel.text = NSLocalizedString(@"helpCentreLabel", @"Title of the section to access MEGA's help centre"); - self.joinBetaLabel.text = NSLocalizedString(@"Join Beta", @"Section title that links you to the webpage that let you join and test the beta versions"); - self.rateUsLabel.text = NSLocalizedString(@"rateUsLabel", @"Title to rate the app"); + self.sendFeedbackLabel.text = LocalizedString(@"sendFeedbackLabel", @"Title of one of the Settings sections where you can 'Send Feedback' to MEGA"); + self.helpCentreLabel.text = LocalizedString(@"helpCentreLabel", @"Title of the section to access MEGA's help centre"); + self.joinBetaLabel.text = LocalizedString(@"Join Beta", @"Section title that links you to the webpage that let you join and test the beta versions"); + self.rateUsLabel.text = LocalizedString(@"rateUsLabel", @"Title to rate the app"); [self updateAppearance]; - self.reportIssueLabel.text = NSLocalizedString(@"help.reportIssue.title", nil); + self.reportIssueLabel.text = LocalizedString(@"help.reportIssue.title", @""); } - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { diff --git a/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueView.swift b/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueView.swift index 2c2bb9e58d..52870d6930 100644 --- a/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueView.swift +++ b/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import MEGASwiftUI import SwiftUI diff --git a/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueViewModel.swift b/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueViewModel.swift index 3386ebe299..6fb13b5967 100644 --- a/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueViewModel.swift +++ b/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n final class ReportIssueViewModel: ObservableObject { private let router: any ReportIssueViewRouting diff --git a/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueViewRouter.swift b/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueViewRouter.swift index 445b106c2e..c9efc5d8af 100644 --- a/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueViewRouter.swift +++ b/iMEGA/My Account/Settings/Help/Report Issue/ReportIssueViewRouter.swift @@ -1,4 +1,3 @@ - import MEGADomain import MEGAPresentation import MEGARepo @@ -21,7 +20,7 @@ protocol ReportIssueViewRouting: Routing { @objc func build() -> UIViewController { let areLogsEnabled = UserDefaults.standard.bool(forKey: "logging") - let sdk = MEGASdkManager.sharedMEGASdk() + let sdk = MEGASdk.shared let compressor = LogFileCompressor() let date = Date() let dateFormatter = DateFormatter() diff --git a/iMEGA/My Account/Settings/Help/Report Issue/UploadLogFileView.swift b/iMEGA/My Account/Settings/Help/Report Issue/UploadLogFileView.swift index 29426e5da3..86b0f020a9 100644 --- a/iMEGA/My Account/Settings/Help/Report Issue/UploadLogFileView.swift +++ b/iMEGA/My Account/Settings/Help/Report Issue/UploadLogFileView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import SwiftUI struct UploadLogFileView: View { diff --git a/iMEGA/My Account/Settings/Help/Send Feedback/SendFeedbackViewModel.swift b/iMEGA/My Account/Settings/Help/Send Feedback/SendFeedbackViewModel.swift index 430eb5871a..45caa1f386 100644 --- a/iMEGA/My Account/Settings/Help/Send Feedback/SendFeedbackViewModel.swift +++ b/iMEGA/My Account/Settings/Help/Send Feedback/SendFeedbackViewModel.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo import MEGASwift diff --git a/iMEGA/My Account/Settings/Help/Send Feedback/SendFeedbackViewRouter.swift b/iMEGA/My Account/Settings/Help/Send Feedback/SendFeedbackViewRouter.swift index 0babbdde47..c29c4990fb 100644 --- a/iMEGA/My Account/Settings/Help/Send Feedback/SendFeedbackViewRouter.swift +++ b/iMEGA/My Account/Settings/Help/Send Feedback/SendFeedbackViewRouter.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation import MessageUI diff --git a/iMEGA/My Account/Settings/QASettings/QASettingsRouter.swift b/iMEGA/My Account/Settings/QASettings/QASettingsRouter.swift index 22454eaaeb..584d81c06c 100644 --- a/iMEGA/My Account/Settings/QASettings/QASettingsRouter.swift +++ b/iMEGA/My Account/Settings/QASettings/QASettingsRouter.swift @@ -1,11 +1,12 @@ import MEGADomain +import MEGAL10n import MEGAPresentation import MEGASDKRepo import SwiftUI protocol QASettingsRouting: Routing { func showAlert(withTitle title: String, message: String, actions: [UIAlertAction]) - func showAlert(withError error: Error) + func showAlert(withError error: any Error) } struct QASettingsRouter: QASettingsRouting { @@ -47,7 +48,7 @@ struct QASettingsRouter: QASettingsRouting { presenter?.present(alertController, animated: true, completion: nil) } - func showAlert(withError error: Error) { + func showAlert(withError error: any Error) { showAlert( withTitle: Constants.errorAlertTitle, message: error.localizedDescription, diff --git a/iMEGA/My Account/Settings/QASettings/QASetttingsViewModel.swift b/iMEGA/My Account/Settings/QASettings/QASetttingsViewModel.swift index fd7d570f5d..2dfd15c007 100644 --- a/iMEGA/My Account/Settings/QASettings/QASetttingsViewModel.swift +++ b/iMEGA/My Account/Settings/QASettings/QASetttingsViewModel.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import SwiftUI final class QASettingsViewModel { @@ -64,7 +65,7 @@ final class QASettingsViewModel { ) } - private func show(error: Error) { + private func show(error: some Error) { router.showAlert(withError: error) } diff --git a/iMEGA/My Account/Settings/Security/MasterKeyViewController.m b/iMEGA/My Account/Settings/Security/MasterKeyViewController.m index ea6d679587..75262f7c81 100644 --- a/iMEGA/My Account/Settings/Security/MasterKeyViewController.m +++ b/iMEGA/My Account/Settings/Security/MasterKeyViewController.m @@ -1,4 +1,3 @@ - #import "MasterKeyViewController.h" #import "MEGAReachabilityManager.h" @@ -9,6 +8,8 @@ #import "NSURL+MNZCategory.h" +@import MEGAL10nObjc; + @interface MasterKeyViewController () @property (weak, nonatomic) IBOutlet UIView *illustrationView; @@ -26,13 +27,13 @@ @implementation MasterKeyViewController - (void)viewDidLoad { [super viewDidLoad]; - self.navigationItem.title = NSLocalizedString(@"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."); + self.navigationItem.title = LocalizedString(@"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."); - [self.carbonCopyMasterKeyButton setTitle:NSLocalizedString(@"copy", @"List option shown on the details of a file or folder") forState:UIControlStateNormal]; + [self.carbonCopyMasterKeyButton setTitle:LocalizedString(@"copy", @"List option shown on the details of a file or folder") forState:UIControlStateNormal]; - [self.saveMasterKey setTitle:NSLocalizedString(@"save", @"Button title to 'Save' the selected option") forState:UIControlStateNormal]; + [self.saveMasterKey setTitle:LocalizedString(@"save", @"Button title to 'Save' the selected option") forState:UIControlStateNormal]; - self.whyDoINeedARecoveryKeyButton.titleLabel.text = NSLocalizedString(@"whyDoINeedARecoveryKey", @"Question button to present a view where it's explained what is the Recovery Key"); + self.whyDoINeedARecoveryKeyButton.titleLabel.text = LocalizedString(@"whyDoINeedARecoveryKey", @"Question button to present a view where it's explained what is the Recovery Key"); [self updateAppearance]; } diff --git a/iMEGA/My Account/Settings/Security/Passcode/PasscodeTableViewController.m b/iMEGA/My Account/Settings/Security/Passcode/PasscodeTableViewController.m index 05969f4bf2..18d9df6e23 100644 --- a/iMEGA/My Account/Settings/Security/Passcode/PasscodeTableViewController.m +++ b/iMEGA/My Account/Settings/Security/Passcode/PasscodeTableViewController.m @@ -1,4 +1,3 @@ - #import "PasscodeTableViewController.h" #import "LTHPasscodeViewController.h" @@ -9,6 +8,8 @@ #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @interface PasscodeTableViewController () { BOOL wasPasscodeAlreadyEnabled; } @@ -30,20 +31,20 @@ @implementation PasscodeTableViewController - (void)viewDidLoad { [super viewDidLoad]; - NSString *title = NSLocalizedString(@"passcode", nil); + NSString *title = LocalizedString(@"passcode", @""); [self.navigationItem setTitle:title]; [self setMenuCapableBackButtonWithMenuTitle:title]; - [self.turnOnOffPasscodeLabel setText:NSLocalizedString(@"passcode", nil)]; - [self.changePasscodeLabel setText:NSLocalizedString(@"changePasscodeLabel", @"Section title where you can change the app's passcode")]; - self.requirePasscodeLabel.text = NSLocalizedString(@"Require Passcode", @"Label indicating that the passcode (pin) view will be displayed if the application goes back to foreground after being x time in background. Examples: require passcode immediately, require passcode after 5 minutes"); + [self.turnOnOffPasscodeLabel setText:LocalizedString(@"passcode", @"")]; + [self.changePasscodeLabel setText:LocalizedString(@"changePasscodeLabel", @"Section title where you can change the app's passcode")]; + self.requirePasscodeLabel.text = LocalizedString(@"Require Passcode", @"Label indicating that the passcode (pin) view will be displayed if the application goes back to foreground after being x time in background. Examples: require passcode immediately, require passcode after 5 minutes"); - self.biometricsLabel.text = NSLocalizedString(@"Touch ID", nil); + self.biometricsLabel.text = LocalizedString(@"Touch ID", @""); LAContext *context = [[LAContext alloc] init]; if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil]) { if (context.biometryType == LABiometryTypeFaceID) { - self.biometricsLabel.text = NSLocalizedString(@"Face ID", nil); + self.biometricsLabel.text = LocalizedString(@"Face ID", @""); } } @@ -83,7 +84,7 @@ - (void)configureView { wasPasscodeAlreadyEnabled = YES; } [[LTHPasscodeViewController sharedUser] setMaxNumberOfAllowedFailedAttempts:10]; - self.requirePasscodeDetailLabel.text = LTHPasscodeViewController.timerDuration > RequirePasscodeAfterImmediatelly ? [NSString mnz_stringFromCallDuration:LTHPasscodeViewController.timerDuration] : NSLocalizedString(@"Immediately", nil); + self.requirePasscodeDetailLabel.text = LTHPasscodeViewController.timerDuration > RequirePasscodeAfterImmediatelly ? [NSString mnz_stringFromCallDuration:LTHPasscodeViewController.timerDuration] : LocalizedString(@"Immediately", @""); } else { [self.biometricsSwitch setOn:NO]; } diff --git a/iMEGA/My Account/Settings/Security/Passcode/PasscodeTimeDurationTableViewController.swift b/iMEGA/My Account/Settings/Security/Passcode/PasscodeTimeDurationTableViewController.swift index a672ee9868..c02350df40 100644 --- a/iMEGA/My Account/Settings/Security/Passcode/PasscodeTimeDurationTableViewController.swift +++ b/iMEGA/My Account/Settings/Security/Passcode/PasscodeTimeDurationTableViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit @objc enum RequirePasscodeAfter: Int { diff --git a/iMEGA/My Account/Settings/Security/QRSettingsTableViewController.h b/iMEGA/My Account/Settings/Security/QRSettingsTableViewController.h index 8966552f23..2b8cb2e5ea 100644 --- a/iMEGA/My Account/Settings/Security/QRSettingsTableViewController.h +++ b/iMEGA/My Account/Settings/Security/QRSettingsTableViewController.h @@ -1,4 +1,3 @@ - #import @interface QRSettingsTableViewController : UITableViewController diff --git a/iMEGA/My Account/Settings/Security/QRSettingsTableViewController.m b/iMEGA/My Account/Settings/Security/QRSettingsTableViewController.m index 967f2f9318..9b96da5e19 100644 --- a/iMEGA/My Account/Settings/Security/QRSettingsTableViewController.m +++ b/iMEGA/My Account/Settings/Security/QRSettingsTableViewController.m @@ -1,4 +1,3 @@ - #import "QRSettingsTableViewController.h" #import "SVProgressHUD.h" @@ -9,6 +8,8 @@ #import "MEGASdkManager.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @interface QRSettingsTableViewController () @property (weak, nonatomic) IBOutlet UIBarButtonItem *closeBarButtonItem; @@ -26,10 +27,10 @@ @implementation QRSettingsTableViewController - (void)viewDidLoad { [super viewDidLoad]; - self.navigationItem.title = NSLocalizedString(@"qrCode", @"QR Code label, used in Settings as title. String as short as possible"); - self.autoAcceptLabel.text = NSLocalizedString(@"autoAccept", @"Label for the setting that allow users to automatically add contacts when they scan his/her QR code. String as short as possible."); - self.resetQRCodeLabel.text = NSLocalizedString(@"resetQrCode", @"Action to reset the current valid QR code of the user"); - self.closeBarButtonItem.title = NSLocalizedString(@"close", nil); + self.navigationItem.title = LocalizedString(@"qrCode", @"QR Code label, used in Settings as title. String as short as possible"); + self.autoAcceptLabel.text = LocalizedString(@"autoAccept", @"Label for the setting that allow users to automatically add contacts when they scan his/her QR code. String as short as possible."); + self.resetQRCodeLabel.text = LocalizedString(@"resetQrCode", @"Action to reset the current valid QR code of the user"); + self.closeBarButtonItem.title = LocalizedString(@"close", @""); self.getContactLinksOptionDelegate = [[MEGAGetAttrUserRequestDelegate alloc] initWithCompletion:^(MEGARequest *request) { self.autoAcceptSwitch.on = request.flag; @@ -66,11 +67,11 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte NSString *footer = @""; switch (section) { case 0: - footer = NSLocalizedString(@"autoAcceptFooter", @"Footer that explains the way Auto-Accept works for QR codes"); + footer = LocalizedString(@"autoAcceptFooter", @"Footer that explains the way Auto-Accept works for QR codes"); break; case 1: - footer = NSLocalizedString(@"resetQrCodeFooter", @"Footer that explains what would happen if the user resets his/her QR code"); + footer = LocalizedString(@"resetQrCodeFooter", @"Footer that explains what would happen if the user resets his/her QR code"); break; default: @@ -88,7 +89,7 @@ - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)ce - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section==1 && indexPath.row == 0) { MEGAContactLinkCreateRequestDelegate *delegate = [[MEGAContactLinkCreateRequestDelegate alloc] initWithCompletion:^(MEGARequest *request) { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"resetQrCodeFooter", @"Footer that explains what would happen if the user resets his/her QR code")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"resetQrCodeFooter", @"Footer that explains what would happen if the user resets his/her QR code")]; }]; [[MEGASdkManager sharedMEGASdk] contactLinkCreateRenew:YES delegate:delegate]; diff --git a/iMEGA/My Account/Settings/Security/SecurityOptionsTableViewController.m b/iMEGA/My Account/Settings/Security/SecurityOptionsTableViewController.m index 359e6b9a83..f4ea4c7d06 100644 --- a/iMEGA/My Account/Settings/Security/SecurityOptionsTableViewController.m +++ b/iMEGA/My Account/Settings/Security/SecurityOptionsTableViewController.m @@ -1,4 +1,3 @@ - #import "SecurityOptionsTableViewController.h" #import "SVProgressHUD.h" @@ -13,6 +12,8 @@ #import "SetupTwoFactorAuthenticationTableViewController.h" #import "QRSettingsTableViewController.h" +@import MEGAL10nObjc; + @interface SecurityOptionsTableViewController () @property (weak, nonatomic) IBOutlet UILabel *twoFactorAuthenticationLabel; @@ -35,19 +36,19 @@ @implementation SecurityOptionsTableViewController - (void)viewDidLoad { [super viewDidLoad]; - NSString *title = NSLocalizedString(@"settings.section.security", @"Title for Security section"); + NSString *title = LocalizedString(@"settings.section.security", @"Title for Security section"); [self.navigationItem setTitle:title]; [self setMenuCapableBackButtonWithMenuTitle:title]; - self.twoFactorAuthenticationLabel.text = NSLocalizedString(@"twoFactorAuthentication", @""); + self.twoFactorAuthenticationLabel.text = LocalizedString(@"twoFactorAuthentication", @""); self.twoFactorAuthenticationRightDetailLabel.text = @""; - self.passcodeLabel.text = NSLocalizedString(@"passcode", @""); + self.passcodeLabel.text = LocalizedString(@"passcode", @""); self.passcodeDetailLabel.text = @""; - self.qrCodeLabel.text = NSLocalizedString(@"qrCode", @"QR Code label, used in Settings as title. String as short as possible"); + self.qrCodeLabel.text = LocalizedString(@"qrCode", @"QR Code label, used in Settings as title. String as short as possible"); - self.closeOtherSessionsLabel.text = NSLocalizedString(@"closeOtherSessions", @"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."); + self.closeOtherSessionsLabel.text = LocalizedString(@"closeOtherSessions", @"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."); [self updateAppearance]; } @@ -87,14 +88,14 @@ - (void)updateAppearance { - (void)twoFactorAuthenticationStatus { MEGAMultiFactorAuthCheckRequestDelegate *delegate = [[MEGAMultiFactorAuthCheckRequestDelegate alloc] initWithCompletion:^(MEGARequest *request, MEGAError *error) { self.twoFactorAuthenticationEnabled = request.flag; - self.twoFactorAuthenticationRightDetailLabel.text = self.twoFactorAuthenticationEnabled ? NSLocalizedString(@"on", nil) : NSLocalizedString(@"off", nil); + self.twoFactorAuthenticationRightDetailLabel.text = self.twoFactorAuthenticationEnabled ? LocalizedString(@"on", @"") : LocalizedString(@"off", @""); [self.tableView reloadData]; }]; [[MEGASdkManager sharedMEGASdk] multiFactorAuthCheckWithEmail:[[MEGASdkManager sharedMEGASdk] myEmail] delegate:delegate]; } - (void)configPasscodeView { - self.passcodeDetailLabel.text = ([LTHPasscodeViewController doesPasscodeExist] ? NSLocalizedString(@"on", nil) : NSLocalizedString(@"off", nil)); + self.passcodeDetailLabel.text = ([LTHPasscodeViewController doesPasscodeExist] ? LocalizedString(@"on", @"") : LocalizedString(@"off", @"")); } - (void)pushQRSettings { @@ -136,17 +137,17 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath case 3: { //Close other sessions if ([MEGAReachabilityManager isReachableHUDIfNot]) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:NSLocalizedString(@"Do you want to close all other sessions? This will log you out on all other active sessions except the current one.", @"Confirmation dialog for the button that logs the user out of all sessions except the current one.") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:LocalizedString(@"Do you want to close all other sessions? This will log you out on all other active sessions except the current one.", @"Confirmation dialog for the button that logs the user out of all sessions except the current one.") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { MEGAGenericRequestDelegate *delegate = [MEGAGenericRequestDelegate.alloc initWithCompletion:^(MEGARequest *request, MEGAError *error) { if (error.type) { - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@", NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:LocalizedString(error.name, @"")]; } - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"sessionsClosed", @"Message shown when you click on 'Close other session' to block every session that is opened on other devices except the current one")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"sessionsClosed", @"Message shown when you click on 'Close other session' to block every session that is opened on other devices except the current one")]; }]; [MEGASdkManager.sharedMEGASdk killSession:-1 delegate:delegate]; }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; break; diff --git a/iMEGA/My Account/Settings/Security/SecuritySettingsViewRouter.swift b/iMEGA/My Account/Settings/Security/SecuritySettingsViewRouter.swift index c6f92aa3dc..59c8073c1a 100644 --- a/iMEGA/My Account/Settings/Security/SecuritySettingsViewRouter.swift +++ b/iMEGA/My Account/Settings/Security/SecuritySettingsViewRouter.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n import MEGAPresentation struct SecuritySettingsViewRouter: Routing { diff --git a/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnabledTwoFactorAuthenticationViewController.h b/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnabledTwoFactorAuthenticationViewController.h index 42d3016a6f..1dade3474d 100644 --- a/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnabledTwoFactorAuthenticationViewController.h +++ b/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnabledTwoFactorAuthenticationViewController.h @@ -1,4 +1,3 @@ - #import @interface EnabledTwoFactorAuthenticationViewController : UIViewController diff --git a/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnabledTwoFactorAuthenticationViewController.m b/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnabledTwoFactorAuthenticationViewController.m index a3edd14e21..5e23962d13 100644 --- a/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnabledTwoFactorAuthenticationViewController.m +++ b/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnabledTwoFactorAuthenticationViewController.m @@ -1,4 +1,3 @@ - #import "EnabledTwoFactorAuthenticationViewController.h" #import "Helper.h" @@ -6,6 +5,8 @@ #import "MEGA-Swift.h" #import "UIApplication+MNZCategory.h" +@import MEGAL10nObjc; + @interface EnabledTwoFactorAuthenticationViewController () @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @@ -29,16 +30,16 @@ @implementation EnabledTwoFactorAuthenticationViewController - (void)viewDidLoad { [super viewDidLoad]; - self.navigationItem.title = NSLocalizedString(@"twoFactorAuthentication", @"A title for the Two-Factor Authentication section on the My Account - Security page."); - self.titleLabel.text = NSLocalizedString(@"twoFactorAuthenticationEnabled", @"A title on the mobile web client page showing that 2FA has been enabled successfully."); - self.firstLabel.text = NSLocalizedString(@"twoFactorAuthenticationEnabledDescription", @"A message on the dialog shown after 2FA was successfully enabled."); - self.secondLabel.text = NSLocalizedString(@"twoFactorAuthenticationEnabledWarning", @"An informational message on the Backup Recovery Key dialog."); - self.recoveryKeyTextField.text = [NSString stringWithFormat:@"%@.txt", NSLocalizedString(@"general.security.recoveryKeyFile", @"Name for the recovery key file")]; + self.navigationItem.title = LocalizedString(@"twoFactorAuthentication", @"A title for the Two-Factor Authentication section on the My Account - Security page."); + self.titleLabel.text = LocalizedString(@"twoFactorAuthenticationEnabled", @"A title on the mobile web client page showing that 2FA has been enabled successfully."); + self.firstLabel.text = LocalizedString(@"twoFactorAuthenticationEnabledDescription", @"A message on the dialog shown after 2FA was successfully enabled."); + self.secondLabel.text = LocalizedString(@"twoFactorAuthenticationEnabledWarning", @"An informational message on the Backup Recovery Key dialog."); + self.recoveryKeyTextField.text = [NSString stringWithFormat:@"%@.txt", LocalizedString(@"general.security.recoveryKeyFile", @"Name for the recovery key file")]; self.recoveryKeyView.layer.borderColor = [UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection].CGColor; - [self.exportRecoveryButton setTitle:NSLocalizedString(@"exportRecoveryKey", @"A dialog title to export the Recovery Key for the current user.") forState:UIControlStateNormal]; - [self.closeButton setTitle:NSLocalizedString(@"close", @"A button label. The button allows the user to close the conversation.") forState:UIControlStateNormal]; + [self.exportRecoveryButton setTitle:LocalizedString(@"exportRecoveryKey", @"A dialog title to export the Recovery Key for the current user.") forState:UIControlStateNormal]; + [self.closeButton setTitle:LocalizedString(@"close", @"A button label. The button allows the user to close the conversation.") forState:UIControlStateNormal]; self.closeButton.layer.borderColor = [UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection].CGColor; [[MEGASdkManager sharedMEGASdk] isMasterKeyExportedWithDelegate:self]; @@ -69,8 +70,8 @@ - (void)updateAppearance { } - (void)showSaveYourRecoveryKeyAlert { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"pleaseSaveYourRecoveryKey", @"A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer.") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"pleaseSaveYourRecoveryKey", @"A warning message on the Backup Recovery Key dialog to tell the user to backup their Recovery Key to their local computer.") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } diff --git a/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnablingTwoFactorAuthenticationViewController.h b/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnablingTwoFactorAuthenticationViewController.h index c19a312990..3c265895ea 100644 --- a/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnablingTwoFactorAuthenticationViewController.h +++ b/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnablingTwoFactorAuthenticationViewController.h @@ -1,4 +1,3 @@ - #import @interface EnablingTwoFactorAuthenticationViewController : UIViewController diff --git a/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnablingTwoFactorAuthenticationViewController.m b/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnablingTwoFactorAuthenticationViewController.m index 5632716a45..bc277e32fc 100644 --- a/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnablingTwoFactorAuthenticationViewController.m +++ b/iMEGA/My Account/Settings/Security/Two Factor Authentication/EnablingTwoFactorAuthenticationViewController.m @@ -1,4 +1,3 @@ - #import "EnablingTwoFactorAuthenticationViewController.h" #import "SVProgressHUD.h" @@ -10,6 +9,8 @@ #import "TwoFactorAuthenticationViewController.h" +@import MEGAL10nObjc; + @interface EnablingTwoFactorAuthenticationViewController () @property (weak, nonatomic) IBOutlet UILabel *firstSectionLabel; @@ -30,7 +31,7 @@ @implementation EnablingTwoFactorAuthenticationViewController - (void)viewDidLoad { [super viewDidLoad]; - NSString *title = NSLocalizedString(@"twoFactorAuthentication", @""); + NSString *title = LocalizedString(@"twoFactorAuthentication", @""); self.navigationItem.title = title; [self setMenuCapableBackButtonWithMenuTitle:title]; @@ -39,8 +40,8 @@ - (void)viewDidLoad { self.seedTextView.text = [self seedSplitInGroupsOfFourCharacters]; - [self.openInButton setTitle:NSLocalizedString(@"openIn", @"Title shown under the action that allows you to open a file in another app") forState:UIControlStateNormal]; - [self.nextButton setTitle:NSLocalizedString(@"next", @"") forState:UIControlStateNormal]; + [self.openInButton setTitle:LocalizedString(@"openIn", @"Title shown under the action that allows you to open a file in another app") forState:UIControlStateNormal]; + [self.nextButton setTitle:LocalizedString(@"next", @"") forState:UIControlStateNormal]; [self.seedTextView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressOnTextView:)]]; @@ -76,7 +77,7 @@ - (void)setupFirstSectionLabelTextAndImage { NSTextAttachment *imageTextAttachment = [[NSTextAttachment alloc] init]; imageTextAttachment.image = [UIImage imageNamed:@"littleQuestionMark"]; imageTextAttachment.bounds = CGRectMake(0, 0, 12.0f, 12.0f); - NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:[NSLocalizedString(@"scanOrCopyTheSeed", @"A message on the setup two-factor authentication page on the mobile web client.") stringByAppendingString:@" "]]; + NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:[LocalizedString(@"scanOrCopyTheSeed", @"A message on the setup two-factor authentication page on the mobile web client.") stringByAppendingString:@" "]]; [mutableAttributedString appendAttributedString:[NSAttributedString attributedStringWithAttachment:imageTextAttachment]]; self.firstSectionLabel.attributedText = mutableAttributedString; } @@ -103,9 +104,9 @@ - (NSString *)seedSplitInGroupsOfFourCharacters { } - (void)youNeedATwoFactorAuthenticationAppAlertWithTitle:(NSString *)message { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Authenticator app required", @"Alert title shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device") message:message preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"App Store", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"Authenticator app required", @"Alert title shown when enabling Two-Factor Authentication when you don't have a two factor authentication app installed on the device") message:message preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"App Store", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { NSURL *url = [NSURL URLWithString:@"itms-apps://itunes.apple.com/search"]; [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:NULL]; }]]; @@ -115,7 +116,7 @@ - (void)youNeedATwoFactorAuthenticationAppAlertWithTitle:(NSString *)message { - (void)firstSectionLabelTapped:(UITapGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateEnded) { - [self youNeedATwoFactorAuthenticationAppAlertWithTitle:NSLocalizedString(@"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.", @"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")]; + [self youNeedATwoFactorAuthenticationAppAlertWithTitle:LocalizedString(@"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.", @"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")]; } } @@ -128,7 +129,7 @@ - (IBAction)openInTouchUpInside:(UIButton *)sender { MEGALogInfo(@"URL opened on authenticator app"); } else { MEGALogInfo(@"URL NOT opened"); - [self youNeedATwoFactorAuthenticationAppAlertWithTitle:NSLocalizedString(@"youNeedATwoFactorAuthenticationApp", @"Alert text shown when enabling the two factor authentication when you don't have a two factor authentication app installed on the device")]; + [self youNeedATwoFactorAuthenticationAppAlertWithTitle:LocalizedString(@"youNeedATwoFactorAuthenticationApp", @"Alert text shown when enabling the two factor authentication when you don't have a two factor authentication app installed on the device")]; } }]; } @@ -148,7 +149,7 @@ - (void)longPressOnTextView:(UILongPressGestureRecognizer *)longPressGestureReco UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; pasteboard.string = self.seed; - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"copiedToTheClipboard", @"Text of the button after the links were copied to the clipboard")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"copiedToTheClipboard", @"Text of the button after the links were copied to the clipboard")]; } } diff --git a/iMEGA/My Account/Settings/Security/Two Factor Authentication/SetupTwoFactorAuthenticationTableViewController.h b/iMEGA/My Account/Settings/Security/Two Factor Authentication/SetupTwoFactorAuthenticationTableViewController.h index 4e9e368e57..fb5b7fbed1 100644 --- a/iMEGA/My Account/Settings/Security/Two Factor Authentication/SetupTwoFactorAuthenticationTableViewController.h +++ b/iMEGA/My Account/Settings/Security/Two Factor Authentication/SetupTwoFactorAuthenticationTableViewController.h @@ -1,4 +1,3 @@ - #import @interface SetupTwoFactorAuthenticationTableViewController : UITableViewController diff --git a/iMEGA/My Account/Settings/Security/Two Factor Authentication/SetupTwoFactorAuthenticationTableViewController.m b/iMEGA/My Account/Settings/Security/Two Factor Authentication/SetupTwoFactorAuthenticationTableViewController.m index d14ee81ec9..6dac75258e 100644 --- a/iMEGA/My Account/Settings/Security/Two Factor Authentication/SetupTwoFactorAuthenticationTableViewController.m +++ b/iMEGA/My Account/Settings/Security/Two Factor Authentication/SetupTwoFactorAuthenticationTableViewController.m @@ -1,4 +1,3 @@ - #import "SetupTwoFactorAuthenticationTableViewController.h" #import "UIApplication+MNZCategory.h" @@ -9,6 +8,8 @@ #import "MEGA-Swift.h" #import "TwoFactorAuthenticationViewController.h" +@import MEGAL10nObjc; + @interface SetupTwoFactorAuthenticationTableViewController () @property (weak, nonatomic) IBOutlet UISwitch *twoFactorAuthenticationSwitch; @@ -25,11 +26,11 @@ @implementation SetupTwoFactorAuthenticationTableViewController - (void)viewDidLoad { [super viewDidLoad]; - NSString *title = NSLocalizedString(@"twoFactorAuthentication", @""); + NSString *title = LocalizedString(@"twoFactorAuthentication", @""); self.navigationItem.title = title; [self setMenuCapableBackButtonWithMenuTitle:title]; - self.twoFactorAuthenticationLabel.text = NSLocalizedString(@"twoFactorAuthentication", @""); + self.twoFactorAuthenticationLabel.text = LocalizedString(@"twoFactorAuthentication", @""); [self updateAppearance]; } @@ -83,7 +84,7 @@ - (IBAction)twoFactorAuthenticationTouchUpInside:(UIButton *)sender { - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { switch (section) { case 0: - return NSLocalizedString(@"whatIsTwoFactorAuthentication", @"Text shown as explanation of what is Two-Factor Authentication"); + return LocalizedString(@"whatIsTwoFactorAuthentication", @"Text shown as explanation of what is Two-Factor Authentication"); default: return @""; diff --git a/iMEGA/My Account/Settings/SettingViewRouter.swift b/iMEGA/My Account/Settings/SettingViewRouter.swift index bf43d05376..a6790fb46e 100644 --- a/iMEGA/My Account/Settings/SettingViewRouter.swift +++ b/iMEGA/My Account/Settings/SettingViewRouter.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation import Settings import UIKit diff --git a/iMEGA/My Account/Settings/SettingsTableViewController+Additions.swift b/iMEGA/My Account/Settings/SettingsTableViewController+Additions.swift index 32787fb8be..bdf13957e7 100644 --- a/iMEGA/My Account/Settings/SettingsTableViewController+Additions.swift +++ b/iMEGA/My Account/Settings/SettingsTableViewController+Additions.swift @@ -1,3 +1,5 @@ +import MEGAL10n + // MARK: UITableViewDelegate extension SettingsTableViewController { diff --git a/iMEGA/My Account/Settings/SettingsTableViewController.m b/iMEGA/My Account/Settings/SettingsTableViewController.m index 97f6167056..76cf63c527 100644 --- a/iMEGA/My Account/Settings/SettingsTableViewController.m +++ b/iMEGA/My Account/Settings/SettingsTableViewController.m @@ -4,6 +4,8 @@ #import "MEGA-Swift.h" #import "NSURL+MNZCategory.h" +@import MEGAL10nObjc; + @interface SettingsTableViewController () @end @@ -18,7 +20,7 @@ - (void)viewDidLoad { self.tableView.estimatedRowHeight = 44; self.tableView.rowHeight = UITableViewAutomaticDimension; [self updateAppearance]; - NSString *title = NSLocalizedString(@"settingsTitle", nil); + NSString *title = LocalizedString(@"settingsTitle", @""); self.navigationItem.title = title; [self setMenuCapableBackButtonWithMenuTitle:title]; } diff --git a/iMEGA/My Account/Settings/SettingsViewModel.swift b/iMEGA/My Account/Settings/SettingsViewModel.swift index 0305d5bb32..a89b755533 100644 --- a/iMEGA/My Account/Settings/SettingsViewModel.swift +++ b/iMEGA/My Account/Settings/SettingsViewModel.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGAPresentation import UIKit diff --git a/iMEGA/My Account/Settings/TermsAndPoliciesScene/TermsAndPoliciesRouter.swift b/iMEGA/My Account/Settings/TermsAndPoliciesScene/TermsAndPoliciesRouter.swift index c64f979040..fd563996ea 100644 --- a/iMEGA/My Account/Settings/TermsAndPoliciesScene/TermsAndPoliciesRouter.swift +++ b/iMEGA/My Account/Settings/TermsAndPoliciesScene/TermsAndPoliciesRouter.swift @@ -1,5 +1,5 @@ - import Foundation +import MEGAL10n import MEGAPresentation import Settings import SwiftUI diff --git a/iMEGA/My Account/Spotlight/SpotlightIndexer.swift b/iMEGA/My Account/Spotlight/SpotlightIndexer.swift index eeee9e73c1..4c854b981e 100644 --- a/iMEGA/My Account/Spotlight/SpotlightIndexer.swift +++ b/iMEGA/My Account/Spotlight/SpotlightIndexer.swift @@ -1,6 +1,7 @@ import Combine import CoreSpotlight import MEGADomain +import MEGASDKRepo import MobileCoreServices final class SpotlightIndexer: NSObject { @@ -26,7 +27,7 @@ final class SpotlightIndexer: NSObject { @objc init(sdk: MEGASdk, passcodeEnabled: Bool = false) { self.sdk = sdk - let favoritesRepository = FavouriteNodesRepository(sdk: sdk) + let favoritesRepository = FavouriteNodesRepository.newRepo self.favouritesUseCase = FavouriteNodesUseCase(repo: favoritesRepository) self.passcodeEnabled = passcodeEnabled super.init() diff --git a/iMEGA/My Account/Two Factor Authentication/TwoFactorAuthenticationViewController.h b/iMEGA/My Account/Two Factor Authentication/TwoFactorAuthenticationViewController.h index 5387ae3d9f..d67596e900 100644 --- a/iMEGA/My Account/Two Factor Authentication/TwoFactorAuthenticationViewController.h +++ b/iMEGA/My Account/Two Factor Authentication/TwoFactorAuthenticationViewController.h @@ -1,4 +1,3 @@ - #import #import "TwoFactorAuthentication.h" diff --git a/iMEGA/My Account/Two Factor Authentication/TwoFactorAuthenticationViewController.m b/iMEGA/My Account/Two Factor Authentication/TwoFactorAuthenticationViewController.m index 0008eaa42c..33ec83e0e9 100644 --- a/iMEGA/My Account/Two Factor Authentication/TwoFactorAuthenticationViewController.m +++ b/iMEGA/My Account/Two Factor Authentication/TwoFactorAuthenticationViewController.m @@ -1,4 +1,3 @@ - #import "TwoFactorAuthenticationViewController.h" #import "SVProgressHUD.h" @@ -13,6 +12,8 @@ #import "MEGANavigationController.h" +@import MEGAL10nObjc; + @interface TwoFactorAuthenticationViewController () @property (weak, nonatomic) IBOutlet UILabel *descriptionLabel; @@ -41,13 +42,13 @@ - (void)viewDidLoad { [self.navigationController setNavigationBarHidden:NO animated:YES]; - self.navigationItem.title = NSLocalizedString(@"twoFactorAuthentication", @""); + self.navigationItem.title = LocalizedString(@"twoFactorAuthentication", @""); - self.descriptionLabel.text = NSLocalizedString(@"pleaseEnterTheSixDigitCode", @"A message on the Verify Login page telling the user to enter their 2FA code."); + self.descriptionLabel.text = LocalizedString(@"pleaseEnterTheSixDigitCode", @"A message on the Verify Login page telling the user to enter their 2FA code."); - self.invalidCodeLabel.text = NSLocalizedString(@"invalidCode", @"Error text shown when the user scans a QR that is not valid. String as short as possible."); + self.invalidCodeLabel.text = LocalizedString(@"invalidCode", @"Error text shown when the user scans a QR that is not valid. String as short as possible."); - [self.lostYourAuthenticatorDeviceButton setTitle:NSLocalizedString(@"lostYourAuthenticatorDevice", @"A button to help them restore their account if they have lost their 2FA device.") forState:UIControlStateNormal]; + [self.lostYourAuthenticatorDeviceButton setTitle:LocalizedString(@"lostYourAuthenticatorDevice", @"A button to help them restore their account if they have lost their 2FA device.") forState:UIControlStateNormal]; if (self.twoFAMode != TwoFactorAuthenticationEnable) { self.lostYourAuthenticatorDeviceButton.hidden = self.lostYourAuthenticatorDeviceImage.hidden = NO; self.lostYourAuthenticatorDeviceButton.enabled = YES; @@ -300,7 +301,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG break; default: - [SVProgressHUD showErrorWithStatus:NSLocalizedString(error.name, nil)]; + [SVProgressHUD showErrorWithStatus:LocalizedString(error.name, @"")]; break; } return; @@ -309,7 +310,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG self.invalidCode = nil; switch (request.type) { case MEGARequestTypeChangePassword: { - [SVProgressHUD showSuccessWithStatus:NSLocalizedString(@"passwordChanged", @"The label showed when your password has been changed")]; + [SVProgressHUD showSuccessWithStatus:LocalizedString(@"passwordChanged", @"The label showed when your password has been changed")]; if (self.twoFAMode == TwoFactorAuthenticationChangePassword) { [self.navigationController dismissViewControllerAnimated:YES completion:nil]; @@ -339,9 +340,9 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG [self.navigationController popToRootViewControllerAnimated:YES]; } } else { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"twoFactorAuthenticationDisabled", @"A message on a dialog to say that 2FA has been successfully disabled.") message:nil preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"twoFactorAuthenticationDisabled", @"A message on a dialog to say that 2FA has been successfully disabled.") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { [self.navigationController popViewControllerAnimated:YES]; }]]; diff --git a/iMEGA/My Account/Upgrade Account/Repository/AccountPlanPurchaseRepository.swift b/iMEGA/My Account/Upgrade Account/Repository/AccountPlanPurchaseRepository.swift index e90f025fc8..a18a6eb0b4 100644 --- a/iMEGA/My Account/Upgrade Account/Repository/AccountPlanPurchaseRepository.swift +++ b/iMEGA/My Account/Upgrade Account/Repository/AccountPlanPurchaseRepository.swift @@ -67,7 +67,6 @@ final class AccountPlanPurchaseRepository: NSObject, AccountPlanPurchaseReposito var accountPlans: [AccountPlanEntity] = [] for (index, product) in products.enumerated() { let plan = product.toAccountPlanEntity( - product: product, storage: storageGB(atProductIndex: index), transfer: transferGB(atProductIndex: index) ) diff --git a/iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase+PromotedPlan.swift b/iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase+PromotedPlan.swift new file mode 100644 index 0000000000..d70e4ddd20 --- /dev/null +++ b/iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase+PromotedPlan.swift @@ -0,0 +1,183 @@ +import MEGADomain +import MEGAL10n +import MEGAPresentation +import MEGASDKRepo +import MEGASwift + +extension MEGAPurchase { + private enum InAppPurchaseStoreError: Error { + case alreadyPurchasedPlan + case noLoggedInUser + case noAccountDetails + case proFlexiOrBusinessAccount + } + + // MARK: - Promoted plan purchase + @objc func shouldAddStorePayment(for product: SKProduct) -> Bool { + SVProgressHUD.show() + do { + return try canProcessStorePayment(for: product) + } catch { + guard let error = error as? InAppPurchaseStoreError else { + SVProgressHUD.dismiss() + return false + } + + if error == .noLoggedInUser { + // Defer until user has logged in, save the product and return false + savePendingPromotedProduct(product) + } else if error == .noAccountDetails { + // Defer until the account details is fetched and return false + fetchAccountDetailsToPurchaseProduct(product) + return false + } + + SVProgressHUD.dismiss() + showStoreErrorMessage(error) + return false + } + } + + @objc func processAnyPendingPromotedPlanPayment() { + guard let pendingProduct = pendingPromotedProductForPayment() else { return } + // Check if payment can still resume + guard shouldAddStorePayment(for: pendingProduct) else { return } + setIsPurchasingPromotedPlan(true) + + // Continuing a previously deferred payment + MEGALogInfo("[StoreKit] Deferred promoted plan \(pendingProduct.localizedTitle) will resume purchase.") + purchaseProduct(pendingProduct) + } + + @objc func handlePromotedPlanPurchaseResult(isSuccess: Bool) { + + guard isSuccess else { + // Failed purchase. Show the default purchase error message. + handleFailedPurchaseWithAlert() + return + } + + // Success purchase. Refresh account details. + // VariantA updates the `currentAccountDetails` on AccountUseCase + // Baseline updates the `mnz_accountDetails` since it is not using the `currentAccountDetails` + SVProgressHUD.show() + Task { + let abTestProvider = DIContainer.abTestProvider + let abValue = await abTestProvider.abTestVariant(for: .upgradePlanRevamp) + + if abValue == .variantA { + await handleRefreshAccountDetailsForVariantA() + } else { + await handleRefreshAccountDetailsForBaseline() + } + + await MainActor.run { SVProgressHUD.dismiss() } + } + } + + // MARK: Purchase result handler + private func handleRefreshAccountDetailsForVariantA() async { + do { + let accountDetails = try await refreshAccountDetails() + NotificationCenter.default.post(name: .refreshAccountDetails, object: accountDetails) + } catch { + MEGALogError("[StoreKit] Error loading account details. Error: \(error)") + } + } + + private func handleRefreshAccountDetailsForBaseline() async { + await withAsyncValue { completion in + MEGASdk.shared.getAccountDetails(with: RequestDelegate { result in + switch result { + case .success(let request): + MEGASdk.shared.mnz_accountDetails = request.megaAccountDetails + NotificationCenter.default.post(name: .refreshAccountDetails, object: nil) + completion(.success) + case .failure(let error): + MEGALogError("[StoreKit] Error loading account details. Error: \(error)") + completion(.success) + } + }) + } + } + + private func handleFailedPurchaseWithAlert() { + let alertController = UIAlertController( + title: Strings.Localizable.failedPurchaseTitle, + message: Strings.Localizable.failedPurchaseMessage, + preferredStyle: .alert + ) + alertController.addAction(UIAlertAction(title: Strings.Localizable.ok, style: .cancel)) + UIApplication.mnz_visibleViewController().present(alertController, animated: true) + } + + // MARK: Helpers + private func canProcessStorePayment(for product: SKProduct) throws -> Bool { + let accountUseCase = AccountUseCase(repository: AccountRepository.newRepo) + + guard accountUseCase.isLoggedIn() else { + MEGALogInfo("[StoreKit] Defer promoted plan \(product.productIdentifier). Purchase will continue after login.") + throw InAppPurchaseStoreError.noLoggedInUser + } + + guard let accountDetails = accountUseCase.currentAccountDetails else { + MEGALogInfo("[StoreKit] No current account details") + throw InAppPurchaseStoreError.noAccountDetails + } + + let isProFlexiOrBusinessAccount = accountDetails.proLevel == .proFlexi || + accountDetails.proLevel == .business + guard !isProFlexiOrBusinessAccount else { + MEGALogInfo("[StoreKit] Cancel promoted plan purchase for \(product.productIdentifier). User's current account is either Pro Flexi or Business account.") + throw InAppPurchaseStoreError.proFlexiOrBusinessAccount + } + + let plan = product.toAccountPlanEntity() + let isProductAlreadyPurchased = plan.type == accountDetails.proLevel && + plan.subscriptionCycle == accountDetails.subscriptionCycle + guard !isProductAlreadyPurchased else { + MEGALogInfo("[StoreKit] Cancel promoted plan purchase. Plan \(product.productIdentifier) is already purchased.") + throw InAppPurchaseStoreError.alreadyPurchasedPlan + } + + MEGALogInfo("[StoreKit] Start promoted plan purchase for \(product.productIdentifier).") + return true + } + + private func refreshAccountDetails() async throws -> AccountDetailsEntity { + let accountUseCase = AccountUseCase(repository: AccountRepository.newRepo) + return try await accountUseCase.refreshCurrentAccountDetails() + } + + private func fetchAccountDetailsToPurchaseProduct(_ product: SKProduct) { + Task { + do { + _ = try await refreshAccountDetails() + + guard shouldAddStorePayment(for: product) else { return } + setIsPurchasingPromotedPlan(true) + + MEGALogInfo("[StoreKit] Deferred promoted plan \(product.localizedTitle) will resume purchase.") + purchaseProduct(product) + } catch { + MEGALogError("[StoreKit] Error loading account details. Error: \(error)") + await SVProgressHUD.dismiss() + await SVProgressHUD.showError(withStatus: Strings.Localizable.somethingWentWrong) + } + } + } + + private func showStoreErrorMessage(_ error: InAppPurchaseStoreError) { + switch error { + case .alreadyPurchasedPlan: + SVProgressHUD.showError(withStatus: Strings.Localizable.UpgradeAccountPlan.Selection.Message.alreadyHaveRecurringSubscriptionOfPlan) + case .noLoggedInUser: + SVProgressHUD.setErrorImage(Asset.Images.Hud.hudError.image) + SVProgressHUD.showError(withStatus: Strings.Localizable.pleaseLogInToYourAccount) + case .proFlexiOrBusinessAccount: + SVProgressHUD.showError(withStatus: Strings.Localizable.Account.Upgrade.NotAvailableWithCurrentPlan.message) + case .noAccountDetails: + return + } + } +} diff --git a/iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase.h b/iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase.h index fec29370ef..c4f49079ac 100644 --- a/iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase.h +++ b/iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase.h @@ -1,4 +1,3 @@ - #import #import #import "MEGASdkManager.h" @@ -13,6 +12,7 @@ @property (nonatomic, strong) NSMutableArray> *restoreDelegateMutableArray; @property (nonatomic, strong) NSMutableArray> *pricingsDelegateMutableArray; @property (nonatomic, strong) MEGAPricing *pricing; +@property (nonatomic, readonly, getter=isPurchasingPromotedPlan) BOOL purchasingPromotedPlan; + (MEGAPurchase *)sharedInstance; - (instancetype)initWithProducts:(NSArray*)products; @@ -22,6 +22,9 @@ - (void)restorePurchase; - (NSUInteger)pricingProductIndexForProduct:(SKProduct *)product; - (void)removeAllProducts; +- (SKProduct *)pendingPromotedProductForPayment; +- (void)savePendingPromotedProduct:(SKProduct *)product; +- (void)setIsPurchasingPromotedPlan:(BOOL)isPurchasing; @end diff --git a/iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase.m b/iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase.m index c7874f4941..ddc144f80b 100644 --- a/iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase.m +++ b/iMEGA/My Account/Upgrade Account/Repository/MEGAPurchase/MEGAPurchase.m @@ -1,15 +1,18 @@ - #import "MEGAPurchase.h" #import "SVProgressHUD.h" #import "MEGA-Swift.h" #import "UIApplication+MNZCategory.h" + +@import MEGAL10nObjc; @import MEGASDKRepo; @interface MEGAPurchase () @property (nonatomic, strong) NSArray *iOSProductIdentifiers; @property (nonatomic, strong) NSMutableArray *products; +@property (nonatomic, strong) SKProduct *pendingStoreProduct; +@property (nonatomic, getter=isPurchasingPromotedPlan) BOOL purchasingPromotedPlan; @end @implementation MEGAPurchase @@ -88,15 +91,17 @@ - (void)purchaseProduct:(SKProduct *)product { } else { MEGALogWarning(@"[StoreKit] In-App purchases is disabled"); - UIAlertController *alertController = [UIAlertController inAppPurchaseAlertWithAppStoreSettingsButton:NSLocalizedString(@"appPurchaseDisabled", @"Error message shown the In App Purchase is disabled in the device Settings") alertMessage:nil]; + UIAlertController *alertController = [UIAlertController inAppPurchaseAlertWithAppStoreSettingsButton:LocalizedString(@"appPurchaseDisabled", @"Error message shown the In App Purchase is disabled in the device Settings") alertMessage:nil]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } } else { MEGALogWarning(@"[StoreKit] Product \"%@\" not found", product.productIdentifier); - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:NSLocalizedString(@"productNotFound", nil), product.productIdentifier] message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:LocalizedString(@"productNotFound", @""), product.productIdentifier] message:nil preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } + + [self savePendingPromotedProduct:nil]; } - (void)restorePurchase { @@ -107,7 +112,7 @@ - (void)restorePurchase { } else { MEGALogWarning(@"[StoreKit] In-App purchases is disabled"); - UIAlertController *alertController = [UIAlertController inAppPurchaseAlertWithAppStoreSettingsButton:NSLocalizedString(@"allowPurchase_title", @"Alert title to remenber the user that needs to enable purchases") alertMessage:NSLocalizedString(@"allowPurchase_message", @"Alert message to remenber the user that needs to enable purchases before continue")]; + UIAlertController *alertController = [UIAlertController inAppPurchaseAlertWithAppStoreSettingsButton:LocalizedString(@"allowPurchase_title", @"Alert title to remenber the user that needs to enable purchases") alertMessage:LocalizedString(@"allowPurchase_message", @"Alert message to remenber the user that needs to enable purchases before continue")]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } } @@ -116,6 +121,20 @@ - (NSUInteger)pricingProductIndexForProduct:(SKProduct *)product { return [self.iOSProductIdentifiers indexOfObject:product.productIdentifier]; } +- (SKProduct *)pendingPromotedProductForPayment { + return self.pendingStoreProduct; +} + +- (void)savePendingPromotedProduct:(SKProduct *)product { + self.pendingStoreProduct = product; +} + +- (void)setIsPurchasingPromotedPlan:(BOOL)isPurchasing { + // If isPurchasing is true, the promoted plan is ongoing + // If isPurchasing is false, the promoted plan purchase is not active or has finished + self.purchasingPromotedPlan = isPurchasing; +} + #pragma mark - SKProductsRequestDelegate Methods - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { @@ -145,7 +164,6 @@ - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { MEGALogError(@"[StoreKit] Request did fail with error %@", error); } - #pragma mark - SKPaymentTransactionObserver Methods - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { @@ -191,7 +209,12 @@ - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)tran [SVProgressHUD dismiss]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; - + + if (self.isPurchasingPromotedPlan) { + [self setIsPurchasingPromotedPlan:NO]; + [self handlePromotedPlanPurchaseResultWithIsSuccess:YES]; + } + break; } @@ -225,6 +248,14 @@ - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)tran [SVProgressHUD dismiss]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; + + if (self.isPurchasingPromotedPlan) { + [self setIsPurchasingPromotedPlan:NO]; + + if (transaction.error.code != SKErrorPaymentCancelled) { + [self handlePromotedPlanPurchaseResultWithIsSuccess:NO]; + } + } break; case SKPaymentTransactionStateDeferred: @@ -262,6 +293,15 @@ - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedW } } +- (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product { + MEGALogDebug(@"[StoreKit] Initiated App store promoted plan purchase"); + + BOOL shouldAddStorePayment = [self shouldAddStorePaymentFor:product]; + [self setIsPurchasingPromotedPlan:shouldAddStorePayment]; + + return shouldAddStorePayment; +} + #pragma mark - MEGARequestDelegate - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { @@ -269,7 +309,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG if (request.type == MEGARequestTypeSubmitPurchaseReceipt) { //MEGAErrorTypeApiEExist is skipped because if a user is downgrading its subscription, this error will be returned by the API, because the receipt does not contain any new information. if (error.type != MEGAErrorTypeApiEExist) { - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:NSLocalizedString(@"wrongPurchase", @"Error message shown when the purchase has failed"), error.name, (long)error.type]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:LocalizedString(@"wrongPurchase", @"Error message shown when the purchase has failed"), error.name, (long)error.type]]; } } return; diff --git a/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanHeaderTagView.swift b/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanHeaderTagView.swift index dd6971258a..3c53a54896 100644 --- a/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanHeaderTagView.swift +++ b/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanHeaderTagView.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import SwiftUI struct PlanHeaderTagView: View { diff --git a/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanPricingView.swift b/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanPricingView.swift index f9a34a4cc4..53361aa23d 100644 --- a/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanPricingView.swift +++ b/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanPricingView.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import SwiftUI struct PlanPricingView: View { @@ -18,7 +19,7 @@ struct PlanPricingView: View { } private var currencyPerTermString: String { - switch plan.term { + switch plan.subscriptionCycle { case .monthly: return Strings.Localizable.UpgradeAccountPlan.Plan.Details.Pricing.localCurrencyPerMonth(plan.currency) case .yearly: diff --git a/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanStorageView.swift b/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanStorageView.swift index aef399fbb4..61d44934bc 100644 --- a/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanStorageView.swift +++ b/iMEGA/My Account/Upgrade Account/Views/Plan View/Sections/PlanStorageView.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import SwiftUI struct PlanStorageView: View { diff --git a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Data/PlanSelectionSnackBarType.swift b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Data/PlanSelectionSnackBarType.swift index 041a6630f0..54bc3be877 100644 --- a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Data/PlanSelectionSnackBarType.swift +++ b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Data/PlanSelectionSnackBarType.swift @@ -1,3 +1,4 @@ +import MEGAL10n enum PlanSelectionSnackBarType { case currentRecurringPlanSelected, none diff --git a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Data/UpgradeAccountPlanAlertType.swift b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Data/UpgradeAccountPlanAlertType.swift index a6647bfb04..bf8f60860f 100644 --- a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Data/UpgradeAccountPlanAlertType.swift +++ b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Data/UpgradeAccountPlanAlertType.swift @@ -1,3 +1,4 @@ +import MEGAL10n enum UpgradeAccountPlanAlertType { case restore(_ status: AlertStatus) @@ -33,7 +34,7 @@ enum UpgradeAccountPlanAlertType { } case .purchase(let status): switch status { - case .failed: return Strings.Localizable.failedRestoreMessage + case .failed: return Strings.Localizable.failedPurchaseMessage default: return "" } diff --git a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionFeatureOfProView.swift b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionFeatureOfProView.swift index d6454b5f44..0db1188115 100644 --- a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionFeatureOfProView.swift +++ b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionFeatureOfProView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct UpgradeSectionFeatureOfProView: View { diff --git a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionHeaderView.swift b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionHeaderView.swift index 11ffb15f04..ca01ae8e5c 100644 --- a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionHeaderView.swift +++ b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionHeaderView.swift @@ -1,9 +1,10 @@ import MEGADomain +import MEGAL10n import SwiftUI struct UpgradeSectionHeaderView: View { var currentPlanName: String - @Binding var selectedTermTab: AccountPlanTermEntity + @Binding var selectedCycleTab: SubscriptionCycleEntity var body: some View { Section { @@ -17,11 +18,11 @@ struct UpgradeSectionHeaderView: View { .bold() .padding(.top, 1) - Picker("Account Plan Term", selection: $selectedTermTab) { + Picker("Account Plan Cycle", selection: $selectedCycleTab) { Text(Strings.Localizable.UpgradeAccountPlan.Header.PlanTermPicker.monthly) - .tag(AccountPlanTermEntity.monthly) + .tag(SubscriptionCycleEntity.monthly) Text(Strings.Localizable.UpgradeAccountPlan.Header.PlanTermPicker.yearly) - .tag(AccountPlanTermEntity.yearly) + .tag(SubscriptionCycleEntity.yearly) } .pickerStyle(.segmented) .frame(width: 200) diff --git a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionSubscriptionView.swift b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionSubscriptionView.swift index 5e38ff7f2c..97cfe64685 100644 --- a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionSubscriptionView.swift +++ b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/Sections/UpgradeSectionSubscriptionView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct UpgradeSectionSubscriptionView: View { diff --git a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/UpgradeAccountPlanView.swift b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/UpgradeAccountPlanView.swift index c543a92205..fc19251de7 100644 --- a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/UpgradeAccountPlanView.swift +++ b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/UpgradeAccountPlanView.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASwiftUI import Settings import SwiftUI @@ -32,7 +33,7 @@ struct UpgradeAccountPlanView: View, DismissibleContentView { ScrollView { LazyVStack(pinnedViews: .sectionFooters) { UpgradeSectionHeaderView(currentPlanName: viewModel.currentPlanName, - selectedTermTab: $viewModel.selectedTermTab) + selectedCycleTab: $viewModel.selectedCycleTab) Section { ForEach(viewModel.filteredPlanList, id: \.self) { plan in diff --git a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/UpgradeAccountPlanViewModel.swift b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/UpgradeAccountPlanViewModel.swift index 286aa8ad1f..4325d83685 100644 --- a/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/UpgradeAccountPlanViewModel.swift +++ b/iMEGA/My Account/Upgrade Account/Views/Upgrade Plan View/UpgradeAccountPlanViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import MEGASdk import MEGASDKRepo import MEGASwift @@ -34,7 +35,7 @@ final class UpgradeAccountPlanViewModel: ObservableObject { private(set) var recommendedPlanType: AccountTypeEntity? var isShowBuyButton = false - @Published var selectedTermTab: AccountPlanTermEntity = .yearly { + @Published var selectedCycleTab: SubscriptionCycleEntity = .yearly { didSet { toggleBuyButton() } } @Published private(set) var selectedPlanType: AccountTypeEntity? { @@ -128,7 +129,7 @@ final class UpgradeAccountPlanViewModel: ObservableObject { setUpPlanTask = Task { planList = await purchaseUseCase.accountPlanProducts() setRecommendedPlan() - await setDefaultPlanTermTab() + await setDefaultPlanCycleTab() await setCurrentPlan(type: accountDetails.proLevel) } } @@ -142,8 +143,8 @@ final class UpgradeAccountPlanViewModel: ObservableObject { } @MainActor - private func setDefaultPlanTermTab() { - selectedTermTab = accountDetails.subscriptionCycle == .monthly ? .monthly : .yearly + private func setDefaultPlanCycleTab() { + selectedCycleTab = accountDetails.subscriptionCycle == .monthly ? .monthly : .yearly } @MainActor @@ -159,8 +160,7 @@ final class UpgradeAccountPlanViewModel: ObservableObject { return plan.type == type } - let term = cycle == .monthly ? AccountPlanTermEntity.monthly : AccountPlanTermEntity.yearly - return plan.type == type && plan.term == term + return plan.type == type && plan.subscriptionCycle == cycle } } @@ -174,7 +174,7 @@ final class UpgradeAccountPlanViewModel: ObservableObject { } var filteredPlanList: [AccountPlanEntity] { - planList.filter { $0.term == selectedTermTab } + planList.filter { $0.subscriptionCycle == selectedCycleTab } } var pricingPageFooterDetails: TextWithLinkDetails { @@ -279,7 +279,7 @@ final class UpgradeAccountPlanViewModel: ObservableObject { default: if plan == currentPlan { return .currentPlan } if let recommendedPlanType, - plan.term == selectedTermTab, + plan.subscriptionCycle == selectedCycleTab, plan.type == recommendedPlanType { return .recommended } diff --git a/iMEGA/My Account/Upgrade/ProductDetailTableViewCell.h b/iMEGA/My Account/Upgrade/ProductDetailTableViewCell.h index a38ff88bc9..a09de75fc2 100644 --- a/iMEGA/My Account/Upgrade/ProductDetailTableViewCell.h +++ b/iMEGA/My Account/Upgrade/ProductDetailTableViewCell.h @@ -1,4 +1,3 @@ - #import @interface ProductDetailTableViewCell : UITableViewCell diff --git a/iMEGA/My Account/Upgrade/ProductDetailTableViewCell.m b/iMEGA/My Account/Upgrade/ProductDetailTableViewCell.m index 20c2a67b1b..0584f4a565 100644 --- a/iMEGA/My Account/Upgrade/ProductDetailTableViewCell.m +++ b/iMEGA/My Account/Upgrade/ProductDetailTableViewCell.m @@ -1,5 +1,3 @@ - - #import "ProductDetailTableViewCell.h" @implementation ProductDetailTableViewCell diff --git a/iMEGA/My Account/Upgrade/ProductDetailViewController.h b/iMEGA/My Account/Upgrade/ProductDetailViewController.h index 370b5c8567..5b2ca7a3bc 100644 --- a/iMEGA/My Account/Upgrade/ProductDetailViewController.h +++ b/iMEGA/My Account/Upgrade/ProductDetailViewController.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/My Account/Upgrade/ProductDetailViewController.m b/iMEGA/My Account/Upgrade/ProductDetailViewController.m index fc68543d9a..c59fc34856 100644 --- a/iMEGA/My Account/Upgrade/ProductDetailViewController.m +++ b/iMEGA/My Account/Upgrade/ProductDetailViewController.m @@ -1,4 +1,3 @@ - #import "ProductDetailViewController.h" #import "MEGANavigationController.h" @@ -8,6 +7,7 @@ #import "ProductDetailTableViewCell.h" #import "UIApplication+MNZCategory.h" +@import MEGAL10nObjc; @import MEGAUIKit; @interface ProductDetailViewController () { @@ -39,26 +39,26 @@ - (void)viewDidLoad { case MEGAAccountTypeLite: [_crestImageView setImage:[UIImage imageNamed:@"white_crest_LITE"]]; self.headerView.backgroundColor = UIColor.mnz_proLITE; - title = NSLocalizedString(@"Pro Lite", nil); + title = LocalizedString(@"Pro Lite", @""); self.selectMembershiptLabel.textColor = UIColor.mnz_proLITE; break; case MEGAAccountTypeProI: [_crestImageView setImage:[UIImage imageNamed:@"white_crest_PROI"]]; [_headerView setBackgroundColor:UIColor.mnz_redProI]; - title = NSLocalizedString(@"Pro I", nil); + title = LocalizedString(@"Pro I", @""); break; case MEGAAccountTypeProII: [_crestImageView setImage:[UIImage imageNamed:@"white_crest_PROII"]]; [_headerView setBackgroundColor:UIColor.mnz_redProII]; - title = NSLocalizedString(@"Pro II", nil); + title = LocalizedString(@"Pro II", @""); break; case MEGAAccountTypeProIII: [_crestImageView setImage:[UIImage imageNamed:@"white_crest_PROIII"]]; [_headerView setBackgroundColor:UIColor.mnz_redProIII]; - title = NSLocalizedString(@"Pro III", nil); + title = LocalizedString(@"Pro III", @""); break; default: @@ -67,7 +67,7 @@ - (void)viewDidLoad { } if (self.currentAccountType == self.megaAccountType) { - UILabel *label = [UILabel.new customNavigationBarLabelWithTitle:NSLocalizedString(@"inAppPurchase.productDetail.navigation.currentPlan", @"A label which shows the user's current PRO plan.") subtitle:title color:UIColor.mnz_label]; + UILabel *label = [UILabel.new customNavigationBarLabelWithTitle:LocalizedString(@"inAppPurchase.productDetail.navigation.currentPlan", @"A label which shows the user's current PRO plan.") subtitle:title color:UIColor.mnz_label]; label.adjustsFontSizeToFitWidth = YES; label.minimumScaleFactor = 0.8f; label.frame = CGRectMake(0, 0, self.navigationItem.titleView.bounds.size.width, 44); @@ -80,17 +80,17 @@ - (void)viewDidLoad { [_bandwidthSizeLabel setText:_bandwidthString]; if (!self.isChoosingTheAccountType) { - UIBarButtonItem *manageBarButtonItem = [UIBarButtonItem.alloc initWithTitle:NSLocalizedString(@"Manage", @"Text indicating to the user some action should be addressed. E.g. Navigate to Settings/File Management to clear cache.") style:UIBarButtonItemStylePlain target:self action:@selector(manageSubscriptions)]; + UIBarButtonItem *manageBarButtonItem = [UIBarButtonItem.alloc initWithTitle:LocalizedString(@"Manage", @"Text indicating to the user some action should be addressed. E.g. Navigate to Settings/File Management to clear cache.") style:UIBarButtonItemStylePlain target:self action:@selector(manageSubscriptions)]; self.navigationItem.rightBarButtonItem = manageBarButtonItem; } [MEGAPurchase.sharedInstance.purchaseDelegateMutableArray addObject:self]; isPurchased = NO; - self.storageLabel.text = NSLocalizedString(@"Storage", @"Label for any ‘Storage’ button, link, text, title, etc. - (String as short as possible)."); - self.bandwidthLabel.text = NSLocalizedString(@"Transfer Quota", @"Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'."); - [_selectMembershiptLabel setText:NSLocalizedString(@"selectMembership", nil)]; - [_save17Label setText:NSLocalizedString(@"save17", nil)]; + self.storageLabel.text = LocalizedString(@"Storage", @"Label for any ‘Storage’ button, link, text, title, etc. - (String as short as possible)."); + self.bandwidthLabel.text = LocalizedString(@"Transfer Quota", @"Some text listed after the amount of transfer quota a user gets with a certain package. For example: '8 TB Transfer quota'."); + [_selectMembershiptLabel setText:LocalizedString(@"selectMembership", @"")]; + [_save17Label setText:LocalizedString(@"save17", @"")]; [self updateAppearance]; [self.tableView sizeHeaderToFit]; @@ -133,7 +133,7 @@ - (void)updateAppearance { } - (void)presentProductUnavailableAlertController { - UIAlertController *alertController = [UIAlertController inAppPurchaseAlertWithAppStoreSettingsButton:NSLocalizedString(@"inAppPurchase.error.alert.title.notAvailable", @"Alert title to remenber the user that needs to enable purchases") alertMessage:nil]; + UIAlertController *alertController = [UIAlertController inAppPurchaseAlertWithAppStoreSettingsButton:LocalizedString(@"inAppPurchase.error.alert.title.notAvailable", @"Alert title to remenber the user that needs to enable purchases") alertMessage:nil]; [self presentViewController:alertController animated:YES completion:nil]; } @@ -144,21 +144,21 @@ - (void)manageSubscriptions { - (void)presentAlreadyHaveActiveSubscriptionAlertWithProduct:(SKProduct *)product { MEGAAccountDetails *accountDetails = MEGASdkManager.sharedMEGASdk.mnz_accountDetails; - NSString *title = NSLocalizedString(@"account.upgrade.alreadyHaveASubscription.title", nil); + NSString *title = LocalizedString(@"account.upgrade.alreadyHaveASubscription.title", @""); NSString *message; BOOL canCancelSubscription = (accountDetails.subscriptionMethodId == MEGAPaymentMethodECP) || (accountDetails.subscriptionMethodId == MEGAPaymentMethodSabadell) || (accountDetails.subscriptionMethodId == MEGAPaymentMethodStripe2); if (canCancelSubscription) { - message = NSLocalizedString(@"account.upgrade.alreadyHaveACancellableSubscription.message", nil); + message = LocalizedString(@"account.upgrade.alreadyHaveACancellableSubscription.message", @""); } else { - message = NSLocalizedString(@"account.upgrade.alreadyHaveASubscription.message", nil); + message = LocalizedString(@"account.upgrade.alreadyHaveASubscription.message", @""); } UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; if (canCancelSubscription) { - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"no", nil) style:UIAlertActionStyleCancel handler:nil]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"yes", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"no", @"") style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"yes", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [MEGASdkManager.sharedMEGASdk creditCardCancelSubscriptions:nil delegate:[MEGAGenericRequestDelegate.alloc initWithCompletion:^(MEGARequest * _Nonnull request, MEGAError * _Nonnull error) { if (error.type == MEGAErrorTypeApiOk) { [[MEGAPurchase sharedInstance] purchaseProduct:product]; @@ -166,7 +166,7 @@ - (void)presentAlreadyHaveActiveSubscriptionAlertWithProduct:(SKProduct *)produc }]]; }]]; } else { - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; } [self presentViewController:alertController animated:YES completion:nil]; } @@ -181,10 +181,10 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N ProductDetailTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"productDetailCell" forIndexPath:indexPath]; if (indexPath.row == 0) { - cell.periodLabel.text = NSLocalizedString(@"monthly", nil); + cell.periodLabel.text = LocalizedString(@"monthly", @""); cell.priceLabel.text = _priceMonthString; } else { - cell.periodLabel.text = NSLocalizedString(@"yearly", nil); + cell.periodLabel.text = LocalizedString(@"yearly", @""); cell.priceLabel.text = _priceYearlyString; } return cell; @@ -237,8 +237,8 @@ - (void)successfulPurchase:(MEGAPurchase *)megaPurchase { - (void)failedPurchase:(NSInteger)errorCode message:(NSString *)errorMessage { if ([self isPurchaseCancelledWithErrorCode:errorCode]) { return; } - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"failedPurchase_title", nil) message:NSLocalizedString(@"failedPurchase_message", nil) preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"failedPurchase_title", @"") message:LocalizedString(@"failedPurchase_message", @"") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; } diff --git a/iMEGA/My Account/Upgrade/UpgradeAccountFactory.swift b/iMEGA/My Account/Upgrade/UpgradeAccountFactory.swift index 314c5734c7..7a1170bb30 100644 --- a/iMEGA/My Account/Upgrade/UpgradeAccountFactory.swift +++ b/iMEGA/My Account/Upgrade/UpgradeAccountFactory.swift @@ -1,4 +1,3 @@ - import Foundation final class UpgradeAccountFactory: NSObject { diff --git a/iMEGA/My Account/Upgrade/UpgradeAccountRouter.swift b/iMEGA/My Account/Upgrade/UpgradeAccountRouter.swift index 20a0adb913..79c3145296 100644 --- a/iMEGA/My Account/Upgrade/UpgradeAccountRouter.swift +++ b/iMEGA/My Account/Upgrade/UpgradeAccountRouter.swift @@ -79,7 +79,7 @@ final class UpgradeAccountRouter { return products.isNotEmpty } - private func handle(error: Error) { + private func handle(error: any Error) { switch error { case UpgradeAccountError.reachability: MEGAReachabilityManager.isReachableHUDIfNot() diff --git a/iMEGA/My Account/Upgrade/UpgradeTableViewController+Additions.swift b/iMEGA/My Account/Upgrade/UpgradeTableViewController+Additions.swift index 88b4a8610c..ef8ac31583 100644 --- a/iMEGA/My Account/Upgrade/UpgradeTableViewController+Additions.swift +++ b/iMEGA/My Account/Upgrade/UpgradeTableViewController+Additions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo extension UpgradeTableViewController { @@ -14,7 +15,7 @@ extension UpgradeTableViewController { } @objc func setCurrentPlanMaxQuotaData() { - guard let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails, + guard let accountDetails = MEGASdk.shared.mnz_accountDetails, let storage = accountDetails.storageMax as? Int64, let transfer = accountDetails.transferMax as? Int64 else { return diff --git a/iMEGA/My Account/Upgrade/UpgradeTableViewController.m b/iMEGA/My Account/Upgrade/UpgradeTableViewController.m index f04e5c908f..356d97d725 100644 --- a/iMEGA/My Account/Upgrade/UpgradeTableViewController.m +++ b/iMEGA/My Account/Upgrade/UpgradeTableViewController.m @@ -16,6 +16,7 @@ #import "ProductDetailViewController.h" #import "ProductTableViewCell.h" +@import MEGAL10nObjc; @import MEGAUIKit; @interface UpgradeTableViewController () @@ -75,25 +76,25 @@ - (void)viewDidLoad { SKProduct *product = MEGAPurchase.sharedInstance.products.firstObject; self.numberFormatter.locale = product.priceLocale; - NSString *navigationTitle = MEGASdkManager.sharedMEGASdk.mnz_isProAccount ? NSLocalizedString(@"Manage Account", @"account management button title in business account’s landing page") : NSLocalizedString(@"upgradeAccount", @"Button title which triggers the action to upgrade your MEGA account level"); - self.title = (self.isChoosingTheAccountType) ? NSLocalizedString(@"chooseYourAccountType", nil) : navigationTitle; + NSString *navigationTitle = MEGASdkManager.sharedMEGASdk.mnz_isProAccount ? LocalizedString(@"Manage Account", @"account management button title in business account’s landing page") : LocalizedString(@"upgradeAccount", @"Button title which triggers the action to upgrade your MEGA account level"); + self.title = (self.isChoosingTheAccountType) ? LocalizedString(@"chooseYourAccountType", @"") : navigationTitle; [self setMenuCapableBackButtonWithMenuTitle:self.title]; - self.chooseFromOneOfThePlansLabel.text = (self.isChoosingTheAccountType) ? NSLocalizedString(@"selectOneAccountType", @"") : NSLocalizedString(@"choosePlan", @"Header that help you with the upgrading process explaining that you have to choose one of the plans below to continue"); + self.chooseFromOneOfThePlansLabel.text = (self.isChoosingTheAccountType) ? LocalizedString(@"selectOneAccountType", @"") : LocalizedString(@"choosePlan", @"Header that help you with the upgrading process explaining that you have to choose one of the plans below to continue"); - self.chooseFromOneOfThePlansProLabel.text = NSLocalizedString(@"choosePlan", @"Header that help you with the upgrading process explaining that you have to choose one of the plans below to continue"); + self.chooseFromOneOfThePlansProLabel.text = LocalizedString(@"choosePlan", @"Header that help you with the upgrading process explaining that you have to choose one of the plans below to continue"); - self.currentPlanLabel.text = NSLocalizedString(@"inAppPurchase.upgrade.label.currentPlan", @"Text shown on the upgrade account page above the current PRO plan subscription"); + self.currentPlanLabel.text = LocalizedString(@"inAppPurchase.upgrade.label.currentPlan", @"Text shown on the upgrade account page above the current PRO plan subscription"); - _autorenewableDescriptionLabel.text = NSLocalizedString(@"autorenewableDescription", @"Describe how works auto-renewable subscriptions on the Apple Store"); + _autorenewableDescriptionLabel.text = LocalizedString(@"autorenewableDescription", @"Describe how works auto-renewable subscriptions on the Apple Store"); - self.termsAndPoliciesBarButtonItem.title = NSLocalizedString(@"settings.section.termsAndPolicies", @"Title of one of the Settings sections where you can see MEGA's 'Terms and Policies'"); + self.termsAndPoliciesBarButtonItem.title = LocalizedString(@"settings.section.termsAndPolicies", @"Title of one of the Settings sections where you can see MEGA's 'Terms and Policies'"); self.navigationController.topViewController.toolbarItems = self.toolbar.items; if (self.presentingViewController || self.navigationController.presentingViewController.presentedViewController == self.navigationController || [self.tabBarController.presentingViewController isKindOfClass:UITabBarController.class]) { self.hideSkipButton = NO; - self.skipBarButtonItem.title = NSLocalizedString(@"skipButton", @"Button title that skips the current action"); + self.skipBarButtonItem.title = LocalizedString(@"skipButton", @"Button title that skips the current action"); } else { self.hideSkipButton = YES; } @@ -101,7 +102,7 @@ - (void)viewDidLoad { if (self.isChoosingTheAccountType) { self.navigationItem.rightBarButtonItem = nil; } else { - UIBarButtonItem *restoreBarButtonItem = [UIBarButtonItem.alloc initWithTitle:NSLocalizedString(@"restore", @"Button title to restore failed purchases") style:UIBarButtonItemStylePlain target:self action:@selector(restoreTouchUpInside)]; + UIBarButtonItem *restoreBarButtonItem = [UIBarButtonItem.alloc initWithTitle:LocalizedString(@"restore", @"Button title to restore failed purchases") style:UIBarButtonItemStylePlain target:self action:@selector(restoreTouchUpInside)]; self.navigationItem.rightBarButtonItem = restoreBarButtonItem; if (self.shouldHideSkipButton) { self.navigationItem.rightBarButtonItem = restoreBarButtonItem; @@ -210,7 +211,7 @@ - (void)setupTableViewHeaderAndFooter { self.footerTopLineView.backgroundColor = [UIColor mnz_separatorForTraitCollection:self.traitCollection]; self.customPlanLabel.textColor = [UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]; - NSString *toUpgradeYourCurrentSubscriptionString = NSLocalizedString(@"To upgrade your current subscription, please contact support for a [A]custom plan[/A].", @"When user is on PRO 3 plan, we will display an extra label to notify user that they can still contact support to have a customised plan."); + NSString *toUpgradeYourCurrentSubscriptionString = LocalizedString(@"To upgrade your current subscription, please contact support for a [A]custom plan[/A].", @"When user is on PRO 3 plan, we will display an extra label to notify user that they can still contact support to have a customised plan."); NSString *customPlanString = [toUpgradeYourCurrentSubscriptionString mnz_stringBetweenString:@"[A]" andString:@"[/A]"]; toUpgradeYourCurrentSubscriptionString = toUpgradeYourCurrentSubscriptionString.mnz_removeWebclientFormatters; NSMutableAttributedString *customPlanMutableAttributedString = [NSMutableAttributedString.new initWithString:toUpgradeYourCurrentSubscriptionString attributes:@{NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; @@ -218,8 +219,8 @@ - (void)setupTableViewHeaderAndFooter { self.customPlanLabel.attributedText = customPlanMutableAttributedString; self.twoMonthsFreeLabel.textColor = [UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection]; - NSMutableAttributedString *asteriskMutableAttributedString = [NSMutableAttributedString.alloc initWithString:NSLocalizedString(@"* ", nil) attributes: @{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_redForTraitCollection:(self.traitCollection)]}]; - NSAttributedString *twoMonthsFreeAttributedString = [NSAttributedString.alloc initWithString:NSLocalizedString(@"twoMonthsFree", @"Text shown in the purchase plan view to explain that annual subscription is 17% cheaper than 12 monthly payments") attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection]}]; + NSMutableAttributedString *asteriskMutableAttributedString = [NSMutableAttributedString.alloc initWithString:LocalizedString(@"* ", @"") attributes: @{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_redForTraitCollection:(self.traitCollection)]}]; + NSAttributedString *twoMonthsFreeAttributedString = [NSAttributedString.alloc initWithString:LocalizedString(@"twoMonthsFree", @"Text shown in the purchase plan view to explain that annual subscription is 17% cheaper than 12 monthly payments") attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection]}]; [asteriskMutableAttributedString appendAttributedString:twoMonthsFreeAttributedString]; self.twoMonthsFreeLabel.attributedText = asteriskMutableAttributedString; self.autorenewableDescriptionLabel.textColor = [UIColor mnz_secondaryGrayForTraitCollection:self.traitCollection]; @@ -231,7 +232,7 @@ - (void)initCurrentPlan { self.currentPlanImageView.image = [self imageForProLevel:self.userProLevel]; self.currentPlanNameView.backgroundColor = [UIColor mnz_colorWithProLevel:self.userProLevel]; - self.currentPlanNameLabel.text = NSLocalizedString([MEGAAccountDetails stringForAccountType:self.userProLevel], nil); + self.currentPlanNameLabel.text = LocalizedString([MEGAAccountDetails stringForAccountType:self.userProLevel], @""); if ([[MEGASdkManager sharedMEGASdk] mnz_isProAccount]) { self.tableView.tableHeaderView = self.chooseFromOneOfThePlansPROHeaderView; @@ -324,7 +325,7 @@ - (UIImage *)imageForProLevel:(MEGAAccountType)proLevel { } - (NSAttributedString *)storageAttributedStringForProLevelAtIndex:(NSInteger)index { - NSString *storageString = NSLocalizedString(@"account.storageQuota", @"Text listed that includes the amount of storage that a user gets with a certain package. For example: '2 TB Storage'."); + NSString *storageString = LocalizedString(@"account.storageQuota", @"Text listed that includes the amount of storage that a user gets with a certain package. For example: '2 TB Storage'."); NSMutableAttributedString *storageMutableAttributedString = [NSMutableAttributedString.alloc initWithString:storageString attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; SKProduct *product = [[MEGAPurchase sharedInstance].products objectOrNilAtIndex:index]; @@ -337,7 +338,7 @@ - (NSAttributedString *)storageAttributedStringForProLevelAtIndex:(NSInteger)ind } - (NSAttributedString *)bandwidthAttributedStringForProLevelAtIndex:(NSInteger)index { - NSString *transferQuotaString = NSLocalizedString(@"account.transferQuota.perMonth", @"Text listed that includes the amount of transfer quota a user gets per month with a certain package. For example: '8 TB Transfer'."); + NSString *transferQuotaString = LocalizedString(@"account.transferQuota.perMonth", @"Text listed that includes the amount of transfer quota a user gets per month with a certain package. For example: '8 TB Transfer'."); NSMutableAttributedString *transferQuotaMutableAttributedString = [NSMutableAttributedString.alloc initWithString:transferQuotaString attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; SKProduct *product = [[MEGAPurchase sharedInstance].products objectOrNilAtIndex:index]; @@ -350,7 +351,7 @@ - (NSAttributedString *)bandwidthAttributedStringForProLevelAtIndex:(NSInteger)i } - (NSAttributedString *)freeStorageAttributedString { - NSString *freeStorageString = NSLocalizedString(@"account.storage.freePlan", @"Text listed that includes the amount of storage that a free user gets"); + NSString *freeStorageString = LocalizedString(@"account.storage.freePlan", @"Text listed that includes the amount of storage that a free user gets"); NSString *freeStorageValueString = [freeStorageString mnz_stringBetweenString:@"[B]" andString:@"[/B]"]; freeStorageString = freeStorageString.mnz_removeWebclientFormatters; NSMutableAttributedString *freeStorageMutableAttributedString = [NSMutableAttributedString.alloc initWithString:freeStorageString attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; @@ -359,14 +360,14 @@ - (NSAttributedString *)freeStorageAttributedString { NSRange freeStorageValueRange = [freeStorageString rangeOfString:freeStorageValueString]; [freeStorageMutableAttributedString replaceCharactersInRange:freeStorageValueRange withAttributedString:storageMutableAttributedString]; - NSAttributedString *superscriptOneAttributedString = [NSAttributedString.alloc initWithString:NSLocalizedString(@" ¹", nil) attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_redForTraitCollection:(self.traitCollection)]}]; + NSAttributedString *superscriptOneAttributedString = [NSAttributedString.alloc initWithString:LocalizedString(@" ¹", @"") attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_redForTraitCollection:(self.traitCollection)]}]; [freeStorageMutableAttributedString appendAttributedString:superscriptOneAttributedString]; return freeStorageMutableAttributedString; } - (NSAttributedString *)freeTransferQuotaAttributedString { - NSString *transferQuotaString = NSLocalizedString(@"account.transferQuota.freePlan", @"Text listed that explain that a free user gets a limited amount of transfer quota."); + NSString *transferQuotaString = LocalizedString(@"account.transferQuota.freePlan", @"Text listed that explain that a free user gets a limited amount of transfer quota."); NSString *limitedString = [transferQuotaString mnz_stringBetweenString:@"[B]" andString:@"[/B]"]; transferQuotaString = transferQuotaString.mnz_removeWebclientFormatters; NSMutableAttributedString *transferQuotaMutableAttributedString = [NSMutableAttributedString.alloc initWithString:transferQuotaString attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; @@ -437,12 +438,12 @@ - (IBAction)requestAPlanTouchUpInside:(UIButton *)sender { mailComposeVC.mailComposeDelegate = self; mailComposeVC.toRecipients = @[@"support@mega.nz"]; - mailComposeVC.subject = NSLocalizedString(@"Upgrade to a custom plan", @"Mail title to upgrade to a custom plan"); - [mailComposeVC setMessageBody:NSLocalizedString(@"Ask us how you can upgrade to a custom plan:", @"Mail subject to upgrade to a custom plan") isHTML:NO]; + mailComposeVC.subject = LocalizedString(@"Upgrade to a custom plan", @"Mail title to upgrade to a custom plan"); + [mailComposeVC setMessageBody:LocalizedString(@"Ask us how you can upgrade to a custom plan:", @"Mail subject to upgrade to a custom plan") isHTML:NO]; [self presentViewController:mailComposeVC animated:YES completion:nil]; } else { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudWarning"] status:NSLocalizedString(@"noEmailAccountConfigured", @"Text shown when you want to send feedback of the app and you don't have an email account set up on your device")]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudWarning"] status:LocalizedString(@"noEmailAccountConfigured", @"Text shown when you want to send feedback of the app and you don't have an email account set up on your device")]; } } @@ -470,9 +471,9 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N ProductTableViewCell *cell; if (self.isChoosingTheAccountType && indexPath.row == 0) { cell = [tableView dequeueReusableCellWithIdentifier:@"freeProductCell" forIndexPath:indexPath]; - NSMutableAttributedString *superscriptOneAttributedString = [NSMutableAttributedString.alloc initWithString:NSLocalizedString(@"¹ ", nil) attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_redForTraitCollection:(self.traitCollection)]}]; + NSMutableAttributedString *superscriptOneAttributedString = [NSMutableAttributedString.alloc initWithString:LocalizedString(@"¹ ", @"") attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_redForTraitCollection:(self.traitCollection)]}]; - NSAttributedString *subjectToYourParticipationAttributedString = [NSAttributedString.alloc initWithString:NSLocalizedString(@"subjectToYourParticipationInOurAchievementsProgram", @"") attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; + NSAttributedString *subjectToYourParticipationAttributedString = [NSAttributedString.alloc initWithString:LocalizedString(@"subjectToYourParticipationInOurAchievementsProgram", @"") attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; [superscriptOneAttributedString appendAttributedString:subjectToYourParticipationAttributedString]; cell.subjectToYourParticipationLabel.attributedText = superscriptOneAttributedString; @@ -482,7 +483,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N NSNumber *proLevelNumber = [self.proLevelsMutableArray objectOrNilAtIndex:indexPath.row]; cell.productImageView.image = [self imageForProLevel:proLevelNumber.integerValue]; - cell.productNameLabel.text = NSLocalizedString([MEGAAccountDetails stringForAccountType:proLevelNumber.integerValue], nil); + cell.productNameLabel.text = LocalizedString([MEGAAccountDetails stringForAccountType:proLevelNumber.integerValue], @""); cell.productNameView.backgroundColor = [UIColor mnz_colorWithProLevel:proLevelNumber.integerValue]; cell.productPriceLabel.textColor = [UIColor mnz_colorForPriceLabelWithProLevel:proLevelNumber.integerValue traitCollection:self.traitCollection]; @@ -492,8 +493,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N SKProduct *product = [[MEGAPurchase sharedInstance].products objectOrNilAtIndex:proLevelIndexNumber.integerValue]; - NSString *productPriceString = [NSString stringWithFormat:NSLocalizedString(@"productPricePerMonth", @"Price asociated with the MEGA PRO account level you can subscribe"), [self.numberFormatter stringFromNumber:product.price]]; - NSAttributedString *asteriskAttributedString = [NSAttributedString.alloc initWithString:NSLocalizedString(@" *", nil) attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_redForTraitCollection:(self.traitCollection)]}]; + NSString *productPriceString = [NSString stringWithFormat:LocalizedString(@"productPricePerMonth", @"Price asociated with the MEGA PRO account level you can subscribe"), [self.numberFormatter stringFromNumber:product.price]]; + NSAttributedString *asteriskAttributedString = [NSAttributedString.alloc initWithString:LocalizedString(@" *", @"") attributes:@{NSFontAttributeName:[UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName:[UIColor mnz_redForTraitCollection:(self.traitCollection)]}]; NSMutableAttributedString *productPriceMutableAttributedString = [NSMutableAttributedString.alloc initWithString:productPriceString attributes:@{NSFontAttributeName : [UIFont mnz_preferredFontWithStyle:UIFontTextStyleCaption1 weight:UIFontWeightMedium], NSForegroundColorAttributeName : [UIColor mnz_colorWithProLevel:proLevelNumber.integerValue]}]; [productPriceMutableAttributedString appendAttributedString:asteriskAttributedString]; cell.productPriceLabel.attributedText = productPriceMutableAttributedString; @@ -523,8 +524,8 @@ - (void)successfulRestore:(MEGAPurchase *)megaPurchase { if (!self.isPurchased) { self.purchased = YES; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"thankYou_title", nil) message:NSLocalizedString(@"purchaseRestore_message", nil) preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"thankYou_title", @"") message:LocalizedString(@"purchaseRestore_message", @"") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { if (UIApplication.mnz_presentingViewController) { [UIApplication.mnz_presentingViewController dismissViewControllerAnimated:YES completion:nil]; } @@ -536,16 +537,16 @@ - (void)successfulRestore:(MEGAPurchase *)megaPurchase { - (void)incompleteRestore { [SVProgressHUD dismiss]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"incompleteRestore_title", @"Alert title shown when a restore hasn't been completed correctly") message:NSLocalizedString(@"incompleteRestore_message", @"Alert message shown when a restore hasn't been completed correctly") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"incompleteRestore_title", @"Alert title shown when a restore hasn't been completed correctly") message:LocalizedString(@"incompleteRestore_message", @"Alert message shown when a restore hasn't been completed correctly") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; } - (void)failedRestore:(NSInteger)errorCode message:(NSString *)errorMessage { [SVProgressHUD dismiss]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"failedRestore_title", @"Alert title shown when the restoring process has stopped for some reason") message:NSLocalizedString(@"failedRestore_message", @"Alert message shown when the restoring process has stopped for some reason") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"failedRestore_title", @"Alert title shown when the restoring process has stopped for some reason") message:LocalizedString(@"failedRestore_message", @"Alert message shown when the restoring process has stopped for some reason") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; } diff --git a/iMEGA/My Account/Usage/UsageViewController+Additions.swift b/iMEGA/My Account/Usage/UsageViewController+Additions.swift index 6089295871..9f21fbcd74 100644 --- a/iMEGA/My Account/Usage/UsageViewController+Additions.swift +++ b/iMEGA/My Account/Usage/UsageViewController+Additions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n extension UsageViewController { @@ -43,9 +44,9 @@ extension UsageViewController { } @objc func initializeStorageInfo() { - guard let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails else { return } + guard let accountDetails = MEGASdk.shared.mnz_accountDetails else { return } - if let rootNode = MEGASdkManager.sharedMEGASdk().rootNode { + if let rootNode = MEGASdk.shared.rootNode { cloudDriveSize = accountDetails.storageUsed(forHandle: rootNode.handle) } @@ -61,7 +62,7 @@ extension UsageViewController { backupsActivityIndicator?.isHidden = true } - if let rubbishNode = MEGASdkManager.sharedMEGASdk().rubbishNode { + if let rubbishNode = MEGASdk.shared.rubbishNode { rubbishBinSize = accountDetails.storageUsed(forHandle: rubbishNode.handle) } @@ -69,8 +70,8 @@ extension UsageViewController { var incomingSharedSizeSum: Int64 = 0 - MEGASdkManager.sharedMEGASdk().inShares().toNodeArray().forEach { node in - incomingSharedSizeSum += MEGASdkManager.sharedMEGASdk().size(for: node).int64Value + MEGASdk.shared.inShares().toNodeArray().forEach { node in + incomingSharedSizeSum += MEGASdk.shared.size(for: node).int64Value } incomingSharesSize = NSNumber(value: incomingSharedSizeSum) @@ -97,14 +98,14 @@ extension UsageViewController { } var isShowPieChartView: Bool { - guard let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails else { + guard let accountDetails = MEGASdk.shared.mnz_accountDetails else { return true } return !(accountDetails.type == .business || accountDetails.type == .proFlexi) } @objc var showTransferQuota: Bool { - guard let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails else { + guard let accountDetails = MEGASdk.shared.mnz_accountDetails else { return false } return Self.shouldShowTransferQuota(accountType: accountDetails.type.toAccountTypeEntity()) diff --git a/iMEGA/My Account/Usage/UsageViewController.m b/iMEGA/My Account/Usage/UsageViewController.m index a906826581..5a5ae8b8c2 100644 --- a/iMEGA/My Account/Usage/UsageViewController.m +++ b/iMEGA/My Account/Usage/UsageViewController.m @@ -8,7 +8,7 @@ #import "Helper.h" @import PieChart; - +@import MEGAL10nObjc; @interface UsageViewController () @end @@ -33,7 +33,7 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - self.navigationItem.title = NSLocalizedString(@"Storage", @"Navigate title for the storage information screen"); + self.navigationItem.title = LocalizedString(@"Storage", @"Navigate title for the storage information screen"); } - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { @@ -94,7 +94,7 @@ - (NSMutableAttributedString *)textForMainLabel:(NSInteger)currentPage { firstPartRange = [firstPartString rangeOfString:firstPartString]; firstPartMutableAttributedString = [NSMutableAttributedString.alloc initWithString:firstPartString]; - NSString *secondPartString = NSLocalizedString(@" %", nil); + NSString *secondPartString = LocalizedString(@" %", @""); secondPartMutableAttributedString = [NSMutableAttributedString.alloc initWithString:secondPartString]; secondPartRange = [secondPartString rangeOfString:secondPartString]; @@ -114,7 +114,7 @@ - (void)textForSecondaryAndTertiaryLabels:(NSInteger)currentPage { firstNumber = self.usedStorage; secondNumber = self.maxStorage; - tertiaryTextString = NSLocalizedString(@"Storage", @"Label for any ‘Storage’ button, link, text, title, etc. - (String as short as possible)."); + tertiaryTextString = LocalizedString(@"Storage", @"Label for any ‘Storage’ button, link, text, title, etc. - (String as short as possible)."); break; } @@ -122,7 +122,7 @@ - (void)textForSecondaryAndTertiaryLabels:(NSInteger)currentPage { firstNumber = self.transferOwnUsed; secondNumber = self.transferMax; - tertiaryTextString = NSLocalizedString(@"Transfer", @"Label to indicate the amount of transfer quota in several places. It is a ‘noun‘ and there is an screenshot with an use example - (String as short as possible)."); + tertiaryTextString = LocalizedString(@"Transfer", @"Label to indicate the amount of transfer quota in several places. It is a ‘noun‘ and there is an screenshot with an use example - (String as short as possible)."); break; } } diff --git a/iMEGA/Node/Links/File/FileLinkViewController+Additions.swift b/iMEGA/Node/Links/File/FileLinkViewController+Additions.swift index 9c3cb78c86..b1314fbce9 100644 --- a/iMEGA/Node/Links/File/FileLinkViewController+Additions.swift +++ b/iMEGA/Node/Links/File/FileLinkViewController+Additions.swift @@ -1,4 +1,3 @@ - extension FileLinkViewController { @objc func download() { guard let publicLinkString = publicLinkString, let linkUrl = URL(string: publicLinkString) else { return } diff --git a/iMEGA/Node/Links/File/FileLinkViewController.m b/iMEGA/Node/Links/File/FileLinkViewController.m index 6d874dd94b..d16b18dc6c 100644 --- a/iMEGA/Node/Links/File/FileLinkViewController.m +++ b/iMEGA/Node/Links/File/FileLinkViewController.m @@ -18,6 +18,7 @@ #import "SendToViewController.h" #import "UnavailableLinkView.h" +@import MEGAL10nObjc; @import MEGAUIKit; @interface FileLinkViewController () @@ -49,7 +50,7 @@ - (void)viewDidLoad { [super viewDidLoad]; self.edgesForExtendedLayout = UIRectEdgeNone; - self.cancelBarButtonItem.title = NSLocalizedString(@"close", @"A button label."); + self.cancelBarButtonItem.title = LocalizedString(@"close", @"A button label."); self.moreBarButtonItem.image = [UIImage imageNamed:@"moreNavigationBar"]; self.navigationItem.leftBarButtonItem = self.cancelBarButtonItem; self.navigationItem.rightBarButtonItem = self.moreBarButtonItem; @@ -57,8 +58,8 @@ - (void)viewDidLoad { self.navigationController.topViewController.toolbarItems = self.toolbar.items; [self.navigationController setToolbarHidden:NO animated:YES]; - [self.openButton setTitle:NSLocalizedString(@"openButton", @"Button title to trigger the action of opening the file without downloading or opening it.") forState:UIControlStateNormal]; - [self.importButton setTitle:NSLocalizedString(@"Import to Cloud Drive", @"Button title that triggers the importing link action") forState:UIControlStateNormal]; + [self.openButton setTitle:LocalizedString(@"openButton", @"Button title to trigger the action of opening the file without downloading or opening it.") forState:UIControlStateNormal]; + [self.importButton setTitle:LocalizedString(@"Import to Cloud Drive", @"Button title that triggers the importing link action") forState:UIControlStateNormal]; [self setUIItemsHidden:YES]; @@ -66,7 +67,7 @@ - (void)viewDidLoad { self.thumbnailImageView.accessibilityIgnoresInvertColors = YES; - self.moreBarButtonItem.accessibilityLabel = NSLocalizedString(@"more", @"Top menu option which opens more menu options in a context menu."); + self.moreBarButtonItem.accessibilityLabel = LocalizedString(@"more", @"Top menu option which opens more menu options in a context menu."); [self updateAppearance]; [self configureContextMenuManager]; @@ -196,12 +197,12 @@ - (void)processRequestResult { - (void)setNavigationBarTitleLabel { if (self.node.name != nil) { - UILabel *label = [UILabel.new customNavigationBarLabelWithTitle:self.node.name subtitle:NSLocalizedString(@"fileLink", nil) color:UIColor.mnz_label]; + UILabel *label = [UILabel.new customNavigationBarLabelWithTitle:self.node.name subtitle:LocalizedString(@"fileLink", @"") color:UIColor.mnz_label]; label.frame = CGRectMake(0, 0, self.navigationItem.titleView.bounds.size.width, 44); self.navigationBarLabel = label; self.navigationItem.titleView = self.navigationBarLabel; } else { - self.navigationItem.title = NSLocalizedString(@"fileLink", nil); + self.navigationItem.title = LocalizedString(@"fileLink", @""); } } @@ -212,7 +213,7 @@ - (void)setUIItemsHidden:(BOOL)boolValue { - (void)showUnavailableLinkViewWithError:(UnavailableLinkError)error { self.moreBarButtonItem.enabled = self.shareLinkBarButtonItem.enabled = self.sendToBarButtonItem.enabled = NO; - self.navigationBarLabel = [UILabel.new customNavigationBarLabelWithTitle:NSLocalizedString(@"fileLink", nil) subtitle:NSLocalizedString(@"Unavailable", @"Text used to show the user that some resource is not available") color:UIColor.mnz_label]; + self.navigationBarLabel = [UILabel.new customNavigationBarLabelWithTitle:LocalizedString(@"fileLink", @"") subtitle:LocalizedString(@"Unavailable", @"Text used to show the user that some resource is not available") color:UIColor.mnz_label]; self.navigationItem.titleView = self.navigationBarLabel; UnavailableLinkView *unavailableLinkView = [[[NSBundle mainBundle] loadNibNamed:@"UnavailableLinkView" owner:self options: nil] firstObject]; switch (error) { @@ -237,21 +238,21 @@ - (void)showUnavailableLinkViewWithError:(UnavailableLinkError)error { } - (void)showDecryptionAlert { - UIAlertController *decryptionAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"decryptionKeyAlertTitle", @"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") message:NSLocalizedString(@"decryptionKeyAlertMessage", @"Alert message 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") preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *decryptionAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"decryptionKeyAlertTitle", @"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") message:LocalizedString(@"decryptionKeyAlertMessage", @"Alert message 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") preferredStyle:UIAlertControllerStyleAlert]; [decryptionAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.placeholder = NSLocalizedString(@"decryptionKey", @"Hint text to suggest that the user has to write the decryption key"); + textField.placeholder = LocalizedString(@"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:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + [decryptionAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { [self dismissViewControllerAnimated:YES completion:nil]; }]]; - UIAlertAction *decryptAlertAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"decrypt", @"Button title to try to decrypt the link") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertAction *decryptAlertAction = [UIAlertAction actionWithTitle:LocalizedString(@"decrypt", @"Button title to try to decrypt the link") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { if ([MEGAReachabilityManager isReachableHUDIfNot]) { NSString *linkString = [MEGALinkManager buildPublicLink:self.publicLinkString withKey:decryptionAlertController.textFields.firstObject.text isFolder:NO]; @@ -276,9 +277,9 @@ - (void)showDecryptionAlert { } - (void)showDecryptionKeyNotValidAlert { - UIAlertController *decryptionKeyNotValidAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"decryptionKeyNotValid", @"Alert title shown when you have written a decryption key not valid") message:nil preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *decryptionKeyNotValidAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"decryptionKeyNotValid", @"Alert title shown when you have written a decryption key not valid") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [decryptionKeyNotValidAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"nil") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [decryptionKeyNotValidAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"nil") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [self showDecryptionAlert]; }]]; diff --git a/iMEGA/Node/Links/Folder/FolderLinkCollectionViewController+DynamicType.swift b/iMEGA/Node/Links/Folder/FolderLinkCollectionViewController+DynamicType.swift index 1c7a6787e2..7423ac8676 100644 --- a/iMEGA/Node/Links/Folder/FolderLinkCollectionViewController+DynamicType.swift +++ b/iMEGA/Node/Links/Folder/FolderLinkCollectionViewController+DynamicType.swift @@ -1,4 +1,3 @@ - extension FolderLinkCollectionViewController: DynamicTypeCollectionViewSizing { func provideSizingCell(for indexPath: IndexPath) -> UICollectionViewCell? { guard let collectionView = collectionView, @@ -8,7 +7,7 @@ extension FolderLinkCollectionViewController: DynamicTypeCollectionViewSizing { NodeCollectionViewCell.instantiateFromFileNib : NodeCollectionViewCell.instantiateFromFolderNib - cell.configureCell(forFolderLinkNode: node, allowedMultipleSelection: collectionView.allowsMultipleSelection, sdk: MEGASdkManager.sharedMEGASdk(), delegate: nil) + cell.configureCell(forFolderLinkNode: node, allowedMultipleSelection: collectionView.allowsMultipleSelection, sdk: .shared, delegate: nil) return cell } diff --git a/iMEGA/Node/Links/Folder/FolderLinkCollectionViewController.swift b/iMEGA/Node/Links/Folder/FolderLinkCollectionViewController.swift index ed341b368f..2ad8c0ee1c 100644 --- a/iMEGA/Node/Links/Folder/FolderLinkCollectionViewController.swift +++ b/iMEGA/Node/Links/Folder/FolderLinkCollectionViewController.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n class FolderLinkCollectionViewController: UIViewController { diff --git a/iMEGA/Node/Links/Folder/FolderLinkTableViewController.swift b/iMEGA/Node/Links/Folder/FolderLinkTableViewController.swift index 2c8764b369..14682f5f6e 100644 --- a/iMEGA/Node/Links/Folder/FolderLinkTableViewController.swift +++ b/iMEGA/Node/Links/Folder/FolderLinkTableViewController.swift @@ -1,6 +1,7 @@ import Foundation import MEGADomain +import MEGAL10n class FolderLinkTableViewController: UIViewController { @@ -126,14 +127,14 @@ extension FolderLinkTableViewController: UITableViewDataSource { private func config(_ cell: NodeTableViewCell, by node: MEGANode, at indexPath: IndexPath) { if node.isFile() { if node.hasThumbnail() { - Helper.thumbnail(for: node, api: MEGASdkManager.sharedMEGASdkFolder(), cell: cell) + Helper.thumbnail(for: node, api: MEGASdk.sharedFolderLink, cell: cell) } else { cell.thumbnailImageView.image = NodeAssetsManager.shared.icon(for: node) } - cell.infoLabel.text = Helper.sizeAndModicationDate(for: node, api: MEGASdkManager.sharedMEGASdkFolder()) + cell.infoLabel.text = Helper.sizeAndModicationDate(for: node, api: MEGASdk.sharedFolderLink) } else if node.isFolder() { cell.thumbnailImageView.image = NodeAssetsManager.shared.icon(for: node) - cell.infoLabel.text = Helper.filesAndFolders(inFolderNode: node, api: MEGASdkManager.sharedMEGASdkFolder()) + cell.infoLabel.text = Helper.filesAndFolders(inFolderNode: node, api: MEGASdk.sharedFolderLink) } cell.thumbnailPlayImageView.isHidden = node.name?.fileExtensionGroup.isVideo != true diff --git a/iMEGA/Node/Links/Folder/FolderLinkViewController+Additions.swift b/iMEGA/Node/Links/Folder/FolderLinkViewController+Additions.swift index 0f47575a33..77fd3def34 100644 --- a/iMEGA/Node/Links/Folder/FolderLinkViewController+Additions.swift +++ b/iMEGA/Node/Links/Folder/FolderLinkViewController+Additions.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n import MEGAPermissions import MEGAPresentation import MEGASDKRepo @@ -140,4 +141,12 @@ extension FolderLinkViewController { } } } + + @objc func selectedCountTitle() -> String { + guard let selectedCount = selectedNodesArray?.count, + selectedCount > 0 else { + return Strings.Localizable.selectTitle + } + return Strings.Localizable.General.Format.itemsSelected(selectedCount) + } } diff --git a/iMEGA/Node/Links/Folder/FolderLinkViewController+SnackBar.swift b/iMEGA/Node/Links/Folder/FolderLinkViewController+SnackBar.swift index 40d854a624..db29289712 100644 --- a/iMEGA/Node/Links/Folder/FolderLinkViewController+SnackBar.swift +++ b/iMEGA/Node/Links/Folder/FolderLinkViewController+SnackBar.swift @@ -1,4 +1,3 @@ - extension FolderLinkViewController: SnackBarPresenting { @MainActor func layout(snackBarView: UIView?) { diff --git a/iMEGA/Node/Links/Folder/FolderLinkViewController.m b/iMEGA/Node/Links/Folder/FolderLinkViewController.m index 56658d07b6..e6995cadd1 100644 --- a/iMEGA/Node/Links/Folder/FolderLinkViewController.m +++ b/iMEGA/Node/Links/Folder/FolderLinkViewController.m @@ -30,6 +30,7 @@ #import "SendToViewController.h" #import "UnavailableLinkView.h" +@import MEGAL10nObjc; @import MEGAUIKit; @interface FolderLinkViewController () @@ -92,19 +93,19 @@ - (void)viewDidLoad { [self setEdgesForExtendedLayout:UIRectEdgeNone]; - self.navigationItem.title = NSLocalizedString(@"folderLink", nil); + self.navigationItem.title = LocalizedString(@"folderLink", @""); self.moreBarButtonItem.title = nil; self.moreBarButtonItem.image = [UIImage imageNamed:@"moreNavigationBar"]; - self.editBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.editBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); self.navigationItem.rightBarButtonItems = @[self.moreBarButtonItem]; self.navigationController.topViewController.toolbarItems = self.toolbar.items; [self.navigationController setToolbarHidden:NO animated:YES]; - self.closeBarButtonItem.title = NSLocalizedString(@"close", @"A button label."); + self.closeBarButtonItem.title = LocalizedString(@"close", @"A button label."); if (self.isFolderRootNode) { self.navigationItem.leftBarButtonItem = self.closeBarButtonItem; @@ -116,7 +117,7 @@ - (void)viewDidLoad { [self determineViewMode]; [self configureContextMenuManager]; - self.moreBarButtonItem.accessibilityLabel = NSLocalizedString(@"more", @"Top menu option which opens more menu options in a context menu."); + self.moreBarButtonItem.accessibilityLabel = LocalizedString(@"more", @"Top menu option which opens more menu options in a context menu."); [self updateAppearance]; @@ -236,16 +237,12 @@ - (void)reloadUI { - (void)setNavigationBarTitleLabel { if (self.flTableView.tableView.isEditing || self.flCollectionView.collectionView.allowsMultipleSelection) { self.navigationItem.titleView = nil; - if (self.selectedNodesArray.count == 0) { - self.navigationItem.title = NSLocalizedString(@"selectTitle", @"Title shown on the Camera Uploads section when the edit mode is enabled. On this mode you can select photos"); - } else { - self.navigationItem.title= (self.selectedNodesArray.count == 1) ? [NSString stringWithFormat:NSLocalizedString(@"oneItemSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected one photo"), self.selectedNodesArray.count] : [NSString stringWithFormat:NSLocalizedString(@"itemsSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected more than one photo"), self.selectedNodesArray.count]; - } + self.navigationItem.title = [self selectedCountTitle]; } else { if (self.parentNode.name && !self.isFolderLinkNotValid) { - self.navigationItem.titleView = [UILabel.new customNavigationBarLabelWithTitle:self.parentNode.name subtitle:NSLocalizedString(@"folderLink", nil) color:UIColor.mnz_label]; + self.navigationItem.titleView = [UILabel.new customNavigationBarLabelWithTitle:self.parentNode.name subtitle:LocalizedString(@"folderLink", @"") color:UIColor.mnz_label]; } else { - self.navigationItem.title = NSLocalizedString(@"folderLink", nil); + self.navigationItem.title = LocalizedString(@"folderLink", @""); } } } @@ -253,7 +250,7 @@ - (void)setNavigationBarTitleLabel { - (void)showUnavailableLinkViewWithError:(UnavailableLinkError)error { [SVProgressHUD dismiss]; - self.navigationItem.titleView = [UILabel.new customNavigationBarLabelWithTitle:self.parentNode.name subtitle: NSLocalizedString(@"Unavailable", @"Text used to show the user that some resource is not available") color:UIColor.mnz_label]; + self.navigationItem.titleView = [UILabel.new customNavigationBarLabelWithTitle:self.parentNode.name subtitle: LocalizedString(@"Unavailable", @"Text used to show the user that some resource is not available") color:UIColor.mnz_label]; [self disableUIItems]; @@ -325,23 +322,23 @@ - (void)hideSearchBarIfNotActive { } - (void)showDecryptionAlert { - UIAlertController *decryptionAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"decryptionKeyAlertTitle", nil) message:NSLocalizedString(@"decryptionKeyAlertMessage", nil) preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *decryptionAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"decryptionKeyAlertTitle", @"") message:LocalizedString(@"decryptionKeyAlertMessage", @"") preferredStyle:UIAlertControllerStyleAlert]; [decryptionAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.placeholder = NSLocalizedString(@"decryptionKey", nil); + textField.placeholder = LocalizedString(@"decryptionKey", @""); [textField addTarget:self action:@selector(decryptionTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { return !textField.text.mnz_isEmpty; }; }]; - [decryptionAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + [decryptionAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { [[MEGASdkManager sharedMEGASdkFolder] logout]; [decryptionAlertController.textFields.firstObject resignFirstResponder]; [self dismissViewControllerAnimated:YES completion:nil]; }]]; - [decryptionAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"decrypt", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [decryptionAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"decrypt", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { NSString *linkString = [MEGALinkManager buildPublicLink:self.publicLinkString withKey:decryptionAlertController.textFields.firstObject.text isFolder:YES]; self.validatingDecryptionKey = YES; @@ -357,8 +354,8 @@ - (void)showDecryptionAlert { - (void)showDecryptionKeyNotValidAlert { self.validatingDecryptionKey = NO; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"decryptionKeyNotValid", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"decryptionKeyNotValid", @"") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { [self showDecryptionAlert]; }]]; [self presentViewController:alertController animated:YES completion:nil]; @@ -545,7 +542,7 @@ - (void)setViewEditing:(BOOL)editing { [self setToolbarButtonsEnabled:!editing]; if (editing) { - self.moreBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.moreBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); self.moreBarButtonItem.image = nil; [self.navigationItem setLeftBarButtonItem:self.selectAllBarButtonItem]; @@ -714,19 +711,19 @@ - (NSString *)titleForEmptyState { if ([MEGAReachabilityManager isReachable]) { if (!self.isFetchNodesDone && self.isFolderRootNode) { if (self.isFolderLinkNotValid) { - text = NSLocalizedString(@"linkNotValid", nil); + text = LocalizedString(@"linkNotValid", @""); } else { text = @""; } } else { if (self.searchController.isActive) { - text = NSLocalizedString(@"noResults", nil); + text = LocalizedString(@"noResults", @""); } else { - text = NSLocalizedString(@"emptyFolder", @"Title shown when a folder doesn't have any files"); + text = LocalizedString(@"emptyFolder", @"Title shown when a folder doesn't have any files"); } } } else { - text = NSLocalizedString(@"noInternetConnection", @"No Internet Connection"); + text = LocalizedString(@"noInternetConnection", @"No Internet Connection"); } return text; @@ -735,7 +732,7 @@ - (NSString *)titleForEmptyState { - (NSString *)descriptionForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); + text = LocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings."); } return text; @@ -763,7 +760,7 @@ - (UIImage *)imageForEmptyState { - (NSString *)buttonTitleForEmptyState { NSString *text = @""; if (!MEGAReachabilityManager.isReachable && !MEGAReachabilityManager.sharedManager.isMobileDataEnabled) { - text = NSLocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); + text = LocalizedString(@"Turn Mobile Data on", @"Button title to go to the iOS Settings to enable 'Mobile Data' for the MEGA app."); } return text; diff --git a/iMEGA/Node/Links/UnavailableLinkView.m b/iMEGA/Node/Links/UnavailableLinkView.m index 90bf48a664..b1e18eddbf 100644 --- a/iMEGA/Node/Links/UnavailableLinkView.m +++ b/iMEGA/Node/Links/UnavailableLinkView.m @@ -2,6 +2,8 @@ #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @implementation UnavailableLinkView #pragma mark - Lifecycle @@ -52,48 +54,48 @@ - (NSAttributedString *)linkAttributedString:(NSString *)text { - (void)configureDescriptionByUserETDSuspension { [self.descriptionLabel addGestureRecognizer:[UITapGestureRecognizer.alloc initWithTarget:self action:@selector(showTerms)]]; self.descriptionLabel.userInteractionEnabled = YES; - self.descriptionLabel.attributedText = [self linkAttributedString:NSLocalizedString(@"This link is unavailable as the user’s account has been closed for gross violation of MEGA’s [A]Terms of Service[/A].", @"Stand-alone error message shown to users who attempt to load/access a link where the user has been suspended/taken-down due to severe violation of our terms of service.")]; + self.descriptionLabel.attributedText = [self linkAttributedString:LocalizedString(@"This link is unavailable as the user’s account has been closed for gross violation of MEGA’s [A]Terms of Service[/A].", @"Stand-alone error message shown to users who attempt to load/access a link where the user has been suspended/taken-down due to severe violation of our terms of service.")]; [self resetLabels]; } - (void)configureDescriptionByUserCopyrightSuspension { [self.descriptionLabel addGestureRecognizer:[UITapGestureRecognizer.alloc initWithTarget:self action:@selector(showTerms)]]; self.descriptionLabel.userInteractionEnabled = YES; - self.descriptionLabel.attributedText = [self linkAttributedString:NSLocalizedString(@"The account that created this link has been terminated due to multiple violations of our [A]Terms of Service[/A].", @"An error message which is shown when you open a file/folder link (or other shared resource) and it’s no longer available because the user account that created the link has been terminated due to multiple violations of our Terms of Service.")]; + self.descriptionLabel.attributedText = [self linkAttributedString:LocalizedString(@"The account that created this link has been terminated due to multiple violations of our [A]Terms of Service[/A].", @"An error message which is shown when you open a file/folder link (or other shared resource) and it’s no longer available because the user account that created the link has been terminated due to multiple violations of our Terms of Service.")]; [self resetLabels]; } - (void)configureDescriptionByLinkETDSuspension { - self.descriptionLabel.text = NSLocalizedString(@"Taken down due to severe violation of our terms of service", @"Stand-alone error message shown to users who attempt to load/access a link where the link has been taken down due to severe violation of our terms of service.");; + self.descriptionLabel.text = LocalizedString(@"Taken down due to severe violation of our terms of service", @"Stand-alone error message shown to users who attempt to load/access a link where the link has been taken down due to severe violation of our terms of service.");; [self resetLabels]; } - (void)configureHeaderInvalidFileLink { self.imageView.image = [UIImage imageNamed:@"invalidFileLink"]; - self.titleLabel.text = NSLocalizedString(@"File link unavailable", @"Error message shown when opening a file link which doesn’t exist"); + self.titleLabel.text = LocalizedString(@"File link unavailable", @"Error message shown when opening a file link which doesn’t exist"); } - (void)configureHeaderInvalidFolderLink { self.imageView.image = [UIImage imageNamed:@"invalidFolderLink"]; - self.titleLabel.text = NSLocalizedString(@"Folder link unavailable", @"Error message shown when opening a folder link which doesn’t exist"); + self.titleLabel.text = LocalizedString(@"Folder link unavailable", @"Error message shown when opening a folder link which doesn’t exist"); } #pragma mark - Public - (void)configureInvalidFolderLink { [self configureHeaderInvalidFolderLink]; - self.descriptionLabel.text = NSLocalizedString(@"folderLinkUnavailableText1", nil); - self.firstTextLabel.text = [NSString stringWithFormat: @"• %@", NSLocalizedString(@"folderLinkUnavailableText2", nil)]; - self.secondTextLabel.text = [NSString stringWithFormat: @"• %@", NSLocalizedString(@"folderLinkUnavailableText3", nil)]; - self.thirdTextLabel.text = [NSString stringWithFormat: @"• %@", NSLocalizedString(@"folderLinkUnavailableText4", nil)]; + self.descriptionLabel.text = LocalizedString(@"folderLinkUnavailableText1", @""); + self.firstTextLabel.text = [NSString stringWithFormat: @"• %@", LocalizedString(@"folderLinkUnavailableText2", @"")]; + self.secondTextLabel.text = [NSString stringWithFormat: @"• %@", LocalizedString(@"folderLinkUnavailableText3", @"")]; + self.thirdTextLabel.text = [NSString stringWithFormat: @"• %@", LocalizedString(@"folderLinkUnavailableText4", @"")]; } - (void)configureInvalidFileLink { [self configureHeaderInvalidFileLink]; - self.descriptionLabel.text = NSLocalizedString(@"fileLinkUnavailableText1", nil); - self.firstTextLabel.text = [NSString stringWithFormat: @"• %@", NSLocalizedString(@"fileLinkUnavailableText2", nil)]; - self.secondTextLabel.text = [NSString stringWithFormat: @"• %@", NSLocalizedString(@"fileLinkUnavailableText3", nil)]; - self.thirdTextLabel.text = [NSString stringWithFormat: @"• %@", NSLocalizedString(@"fileLinkUnavailableText4", nil)]; + self.descriptionLabel.text = LocalizedString(@"fileLinkUnavailableText1", @""); + self.firstTextLabel.text = [NSString stringWithFormat: @"• %@", LocalizedString(@"fileLinkUnavailableText2", @"")]; + self.secondTextLabel.text = [NSString stringWithFormat: @"• %@", LocalizedString(@"fileLinkUnavailableText3", @"")]; + self.thirdTextLabel.text = [NSString stringWithFormat: @"• %@", LocalizedString(@"fileLinkUnavailableText4", @"")]; } - (void)configureInvalidFileLinkByETD { @@ -128,7 +130,7 @@ - (void)configureInvalidFolderLinkByUserCopyrightSuspension { - (void)configureInvalidQueryLink { self.imageView.image = [UIImage imageNamed:@"invalidFileLink"]; - self.titleLabel.text = NSLocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid"); + self.titleLabel.text = LocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid"); self.descriptionLabel.text = @""; [self resetLabels]; } diff --git a/iMEGA/Node/NameCollisionScene/ActionsView/ActionViewModel.swift b/iMEGA/Node/NameCollisionScene/ActionsView/ActionViewModel.swift index 514d2f5727..8b24939333 100644 --- a/iMEGA/Node/NameCollisionScene/ActionsView/ActionViewModel.swift +++ b/iMEGA/Node/NameCollisionScene/ActionsView/ActionViewModel.swift @@ -1,3 +1,4 @@ +import MEGAL10n final class ActionViewModel: ObservableObject { diff --git a/iMEGA/Node/NameCollisionScene/HeaderView/HeaderViewModel.swift b/iMEGA/Node/NameCollisionScene/HeaderView/HeaderViewModel.swift index d07840362b..135f415497 100644 --- a/iMEGA/Node/NameCollisionScene/HeaderView/HeaderViewModel.swift +++ b/iMEGA/Node/NameCollisionScene/HeaderView/HeaderViewModel.swift @@ -1,3 +1,4 @@ +import MEGAL10n final class HeaderViewModel: ObservableObject { private let isFile: Bool diff --git a/iMEGA/Node/NameCollisionScene/NameCollisionRouter.swift b/iMEGA/Node/NameCollisionScene/NameCollisionRouter.swift index 270fe3c944..807d57d63f 100644 --- a/iMEGA/Node/NameCollisionScene/NameCollisionRouter.swift +++ b/iMEGA/Node/NameCollisionScene/NameCollisionRouter.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGARepo import MEGASDKRepo import SwiftUI @@ -48,7 +49,7 @@ final class NameCollisionViewRouter: NameCollisionViewRouting { baseViewController = viewController viewModel?.checkNameCollisions() } - + func dismiss() { SVProgressHUD.setDefaultMaskType(.none) SVProgressHUD.dismiss() @@ -75,13 +76,15 @@ final class NameCollisionViewRouter: NameCollisionViewRouting { #endif } } - - func showCopyOrMoveSuccess() { + + @MainActor + func showCopyOrMoveSuccess() async { dismiss() SVProgressHUD.showSuccess(withStatus: Strings.Localizable.completed) } - - func showCopyOrMoveError() { + + @MainActor + func showCopyOrMoveError() async { dismiss() SVProgressHUD.showError(withStatus: Strings.Localizable.somethingWentWrong) } diff --git a/iMEGA/Node/NameCollisionScene/NameCollisionView/NameCollisionView.swift b/iMEGA/Node/NameCollisionScene/NameCollisionView/NameCollisionView.swift index 2f6cef1f14..b80b1ca04f 100644 --- a/iMEGA/Node/NameCollisionScene/NameCollisionView/NameCollisionView.swift +++ b/iMEGA/Node/NameCollisionScene/NameCollisionView/NameCollisionView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct NameCollisionView: View { diff --git a/iMEGA/Node/NameCollisionScene/NameCollisionView/NameCollisionViewModel.swift b/iMEGA/Node/NameCollisionScene/NameCollisionView/NameCollisionViewModel.swift index da956225aa..7bca5d4819 100644 --- a/iMEGA/Node/NameCollisionScene/NameCollisionView/NameCollisionViewModel.swift +++ b/iMEGA/Node/NameCollisionScene/NameCollisionView/NameCollisionViewModel.swift @@ -5,8 +5,8 @@ protocol NameCollisionViewRouting: Routing { func showNameCollisionsView() func resolvedUploadCollisions(_ transfers: [CancellableTransfer]) func dismiss() - func showCopyOrMoveSuccess() - func showCopyOrMoveError() + func showCopyOrMoveSuccess() async + func showCopyOrMoveError() async func showProgressIndicator() } @@ -235,23 +235,38 @@ final class NameCollisionViewModel: ObservableObject { router.resolvedUploadCollisions(transfers ?? []) case .move: Task { - let moveNodeHandles = try await nameCollisionUseCase.moveNodesFromResolvedCollisions(collisions) - await finishedTask(for: moveNodeHandles) + await processMoveCollisions() } case .copy: Task { - let copyNodeHandles = try await nameCollisionUseCase.copyNodesFromResolvedCollisions(collisions, isFolderLink: isFolderLink) - await finishedTask(for: copyNodeHandles) + await processCopyCollisions() } } } - - @MainActor - private func finishedTask(for handles: [HandleEntity]) { + + private func processMoveCollisions() async { + do { + let moveNodeHandles = try await nameCollisionUseCase.moveNodesFromResolvedCollisions(collisions) + await finishedTask(for: moveNodeHandles) + } catch { + await router.showCopyOrMoveError() + } + } + + private func processCopyCollisions() async { + do { + let copyNodeHandles = try await nameCollisionUseCase.copyNodesFromResolvedCollisions(collisions, isFolderLink: isFolderLink) + await finishedTask(for: copyNodeHandles) + } catch { + await router.showCopyOrMoveError() + } + } + + private func finishedTask(for handles: [HandleEntity]) async { if handles.count == collisions.count { - router.showCopyOrMoveSuccess() + await router.showCopyOrMoveSuccess() } else { - router.showCopyOrMoveError() + await router.showCopyOrMoveError() } } diff --git a/iMEGA/Node/Photo browser/MEGAPhotoBrowserAnimator.h b/iMEGA/Node/Photo browser/MEGAPhotoBrowserAnimator.h index 9ac2ce8abb..764835131d 100644 --- a/iMEGA/Node/Photo browser/MEGAPhotoBrowserAnimator.h +++ b/iMEGA/Node/Photo browser/MEGAPhotoBrowserAnimator.h @@ -1,4 +1,3 @@ - #import typedef NS_ENUM(NSUInteger, MEGAPhotoBrowserAnimatorMode) { diff --git a/iMEGA/Node/Photo browser/MEGAPhotoBrowserAnimator.m b/iMEGA/Node/Photo browser/MEGAPhotoBrowserAnimator.m index 082387632a..84b69f7cf6 100644 --- a/iMEGA/Node/Photo browser/MEGAPhotoBrowserAnimator.m +++ b/iMEGA/Node/Photo browser/MEGAPhotoBrowserAnimator.m @@ -1,4 +1,3 @@ - #import "MEGAPhotoBrowserAnimator.h" @interface MEGAPhotoBrowserAnimator () diff --git a/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerCollectionViewCell.h b/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerCollectionViewCell.h index a91b199067..2e7c3d259d 100644 --- a/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerCollectionViewCell.h +++ b/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerCollectionViewCell.h @@ -1,4 +1,3 @@ - #import @interface MEGAPhotoBrowserPickerCollectionViewCell : UICollectionViewCell diff --git a/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerCollectionViewCell.m b/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerCollectionViewCell.m index 8920e1d2fd..f18c980f72 100644 --- a/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerCollectionViewCell.m +++ b/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerCollectionViewCell.m @@ -1,4 +1,3 @@ - #import "MEGAPhotoBrowserPickerCollectionViewCell.h" @implementation MEGAPhotoBrowserPickerCollectionViewCell diff --git a/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerViewController.h b/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerViewController.h index c7e7eada98..dc1ee25029 100644 --- a/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerViewController.h +++ b/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerViewController.h @@ -1,4 +1,3 @@ - #import @class MEGAPhotoBrowserPickerViewController; diff --git a/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerViewController.m b/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerViewController.m index 72cc878710..5126b1f7c1 100644 --- a/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerViewController.m +++ b/iMEGA/Node/Photo browser/MEGAPhotoBrowserPickerViewController.m @@ -1,4 +1,3 @@ - #import "MEGAPhotoBrowserPickerViewController.h" #import "Helper.h" @@ -11,6 +10,7 @@ #import "MEGA-Swift.h" +@import MEGAL10nObjc; @import MEGAUIKit; @interface MEGAPhotoBrowserPickerViewController () @@ -35,10 +35,10 @@ - (void)viewDidLayoutSubviews { self.cellSize = [self.collectionView mnz_calculateCellSizeForInset:self.cellInset]; [self.collectionView.collectionViewLayout invalidateLayout]; - self.closeBarButtonItem.title = NSLocalizedString(@"close", @"A button label."); + self.closeBarButtonItem.title = LocalizedString(@"close", @"A button label."); NSString *folderName = [self.api nodeForHandle:self.mediaNodes.firstObject.parentHandle].name; - NSString *numberOfFiles = [NSString stringWithFormat:NSLocalizedString(@"general.format.count.file", @"Subtitle shown on folders that gives you information about its file content count. e.g 1 file, 2 files"), self.mediaNodes.count]; + NSString *numberOfFiles = [NSString stringWithFormat:LocalizedString(@"general.format.count.file", @"Subtitle shown on folders that gives you information about its file content count. e.g 1 file, 2 files"), self.mediaNodes.count]; if (!folderName) { folderName = numberOfFiles; diff --git a/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+Additions.swift b/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+Additions.swift index 2e58eaa40d..09a4a386f2 100644 --- a/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+Additions.swift +++ b/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+Additions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPermissions import MEGASDKRepo import MEGASwiftUI @@ -47,7 +48,7 @@ extension MEGAPhotoBrowserViewController { } if node.mnz_isPlayable() { - guard !MEGASdkManager.sharedMEGAChatSdk().mnz_existsActiveCall else { + guard !MEGAChatSdk.sharedChatSdk.mnz_existsActiveCall else { Helper.cannotPlayContentDuringACallAlert() return } @@ -90,7 +91,8 @@ extension MEGAPhotoBrowserViewController { permissionHandler.photosPermissionWithCompletionHandler {[weak self] granted in guard let self else { return } if granted { - let saveMediaUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: MEGASdkManager.sharedMEGASdk(), sharedFolderSdk: self.displayMode == .nodeInsideFolderLink ? self.api : nil), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) + let saveMediaUseCase = dataProvider.makeSaveMediaToPhotosUseCase(for: displayMode) + let completionBlock: (Result) -> Void = { result in if case let .failure(error) = result, error != .cancelled { SVProgressHUD.dismiss() @@ -101,8 +103,6 @@ extension MEGAPhotoBrowserViewController { } } - TransfersWidgetViewController.sharedTransfer().bringProgressToFrontKeyWindowIfNeeded() - switch self.displayMode { case .chatAttachment, .chatSharedFiles: saveMediaUseCase.saveToPhotosChatNode(handle: node.handle, messageId: self.messageId, chatId: self.chatId, completion: completionBlock) @@ -113,15 +113,19 @@ extension MEGAPhotoBrowserViewController { default: Task { @MainActor in do { + SnackBarRouter.shared.present(snackBar: SnackBar(message: Strings.Localizable.General.SaveToPhotos.started(1))) try await saveMediaUseCase.saveToPhotos(nodes: [node.toNodeEntity()]) + } catch let error as SaveMediaToPhotosErrorEntity where error == .fileDownloadInProgress { + SnackBarRouter.shared.dismissSnackBar(immediate: true) + SnackBarRouter.shared.present(snackBar: SnackBar(message: error.localizedDescription)) + } catch let error as SaveMediaToPhotosErrorEntity where error != .cancelled { + await SVProgressHUD.dismiss() + SVProgressHUD.show( + Asset.Images.NodeActions.saveToPhotos.image, + status: error.localizedDescription + ) } catch { - if let errorEntity = error as? SaveMediaToPhotosErrorEntity, errorEntity != .cancelled { - await SVProgressHUD.dismiss() - SVProgressHUD.show( - Asset.Images.NodeActions.saveToPhotos.image, - status: error.localizedDescription - ) - } + MEGALogError("[MEGAPhotoBrowserViewController] Error saving media nodes: \(error)") } } } @@ -176,7 +180,7 @@ extension MEGAPhotoBrowserViewController { } @objc func viewNodeInFolder(_ node: MEGANode) { - guard let parentNode = MEGASdkManager.sharedMEGASdk().node(forHandle: node.parentHandle), + guard let parentNode = MEGASdk.sharedSdk.node(forHandle: node.parentHandle), parentNode.isFolder() else { return } diff --git a/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+LiveText.swift b/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+LiveText.swift index 027c00b781..eed3e0e8dd 100644 --- a/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+LiveText.swift +++ b/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+LiveText.swift @@ -1,9 +1,8 @@ - extension MEGAPhotoBrowserViewController { @objc func imageView(frame: CGRect) -> UIImageView { guard #available(iOS 16, *) else { - return UIImageView(frame: frame) + return SDAnimatedImageView(frame: frame) } return LiveTextImageView(frame: frame) } diff --git a/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+SnackBarPresenting.swift b/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+SnackBarPresenting.swift new file mode 100644 index 0000000000..f457e00404 --- /dev/null +++ b/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController+SnackBarPresenting.swift @@ -0,0 +1,34 @@ +import Foundation + +extension MEGAPhotoBrowserViewController: SnackBarPresenting { + @MainActor + func layout(snackBarView: UIView?) { + snackBarContainer?.removeFromSuperview() + snackBarContainer = snackBarView + snackBarContainer?.backgroundColor = .clear + + guard let snackBarView else { + return + } + + snackBarView.translatesAutoresizingMaskIntoConstraints = false + let toolbarHeight = toolbar?.frame.height ?? 0 + let bottomOffset: CGFloat = self.view.safeAreaInsets.bottom + + [snackBarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + snackBarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + snackBarView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -(bottomOffset + toolbarHeight))].activate() + } + + func snackBarContainerView() -> UIView? { + snackBarContainer + } + + @objc func configureSnackBarPresenter() { + SnackBarRouter.shared.configurePresenter(self) + } + + @objc func removeSnackBarPresenter() { + SnackBarRouter.shared.removePresenter() + } +} diff --git a/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController.h b/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController.h index f110f7149e..b1ff476612 100644 --- a/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController.h +++ b/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController.h @@ -1,4 +1,3 @@ - #import #import "DisplayMode.h" @@ -32,8 +31,10 @@ typedef NS_ENUM(NSUInteger, MEGAPhotoMode) { @property (nonatomic) NSArray *messagesIds; @property (weak, nonatomic) IBOutlet UIBarButtonItem *centerToolbarItem; +@property (weak, nonatomic) IBOutlet UIToolbar *toolbar; @property (strong, nonatomic) PhotoBrowserDataProvider *dataProvider; +@property (nonatomic, strong, nullable) UIView *snackBarContainer; - (void)reloadUI; - (void)configureNodeIntoImage:(MEGANode *) node nodeIndex:(NSUInteger) index; diff --git a/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController.m b/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController.m index 36a7784551..1a804f991d 100644 --- a/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController.m +++ b/iMEGA/Node/Photo browser/MEGAPhotoBrowserViewController.m @@ -1,4 +1,3 @@ - #import "MEGAPhotoBrowserViewController.h" #import "PieChartView.h" @@ -26,6 +25,7 @@ #import "MEGA-Swift.h" #import "NSArray+MNZCategory.h" +@import MEGAL10nObjc; @import MEGAUIKit; @import MEGASDKRepo; @@ -41,7 +41,6 @@ @interface MEGAPhotoBrowserViewController () some SaveMediaToPhotosUseCaseProtocol { + SaveMediaToPhotosUseCase( + downloadFileRepository: DownloadFileRepository( + sdk: sdk, + sharedFolderSdk: displayMode == .nodeInsideFolderLink ? sdk : nil, + nodeProvider: nodeProvider), + fileCacheRepository: FileCacheRepository.newRepo, + nodeRepository: NodeRepository.newRepo) + } private func isValid(index: Int) -> Bool { if let nodes = megaNodes { diff --git a/iMEGA/Node/PhotosAppBrowser/DataModel/Album.swift b/iMEGA/Node/PhotosAppBrowser/DataModel/Album.swift index 1fcee22ec1..eb83285db4 100644 --- a/iMEGA/Node/PhotosAppBrowser/DataModel/Album.swift +++ b/iMEGA/Node/PhotosAppBrowser/DataModel/Album.swift @@ -1,4 +1,3 @@ - import Photos import UIKit diff --git a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/AssetDownloader.swift b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/AssetDownloader.swift index 8a489c00ed..422aff067a 100644 --- a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/AssetDownloader.swift +++ b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/AssetDownloader.swift @@ -1,4 +1,3 @@ - import Photos import UIKit diff --git a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/Extensions/UIViewController+PhotoAlbumBrowserAdditions.swift b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/Extensions/UIViewController+PhotoAlbumBrowserAdditions.swift index 067f3f5d03..04a0a373de 100644 --- a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/Extensions/UIViewController+PhotoAlbumBrowserAdditions.swift +++ b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/Extensions/UIViewController+PhotoAlbumBrowserAdditions.swift @@ -1,4 +1,3 @@ - import UIKit extension UIViewController { diff --git a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoCarouselVideoIcon.swift b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoCarouselVideoIcon.swift index d43492ad5a..b4058a9445 100644 --- a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoCarouselVideoIcon.swift +++ b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoCarouselVideoIcon.swift @@ -1,4 +1,3 @@ - import UIKit final class PhotoCarouselVideoIcon: SingleTapView { diff --git a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoCollectionBottomView.swift b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoCollectionBottomView.swift index 246fde9558..ba29da9da7 100644 --- a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoCollectionBottomView.swift +++ b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoCollectionBottomView.swift @@ -1,4 +1,3 @@ - import UIKit final class PhotoCollectionBottomView: UIView { diff --git a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoSelectedMarkerView.swift b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoSelectedMarkerView.swift index cf2598488d..6e6f6f60fe 100644 --- a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoSelectedMarkerView.swift +++ b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/HelperViews/PhotoSelectedMarkerView.swift @@ -1,4 +1,3 @@ - import UIKit final class PhotoSelectedMarkerView: SingleTapView { diff --git a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/SingleTapHandlerProtocol.swift b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/SingleTapHandlerProtocol.swift index a33d6790bc..b06d699bc2 100644 --- a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/SingleTapHandlerProtocol.swift +++ b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/SingleTapHandlerProtocol.swift @@ -1,4 +1,3 @@ - protocol SingleTapHandlerProtocol { var singleTapGesture: UITapGestureRecognizer { get } var singleTapHandler: (() -> Void)? { get set } diff --git a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/SingleTapView.swift b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/SingleTapView.swift index 69c24d1074..bc323368e4 100644 --- a/iMEGA/Node/PhotosAppBrowser/Miscellaneous/SingleTapView.swift +++ b/iMEGA/Node/PhotosAppBrowser/Miscellaneous/SingleTapView.swift @@ -1,4 +1,3 @@ - class SingleTapView: UIView, SingleTapHandlerProtocol { // MARK: - SingleTapHandlerProtocol diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsSelectionActionType.swift b/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsSelectionActionType.swift index 91d46afd03..bc0e16a8aa 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsSelectionActionType.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsSelectionActionType.swift @@ -1,3 +1,4 @@ +import MEGAL10n @objc enum AlbumsSelectionActionType: Int, CaseIterable { case send diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsTableViewDataSource.swift b/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsTableViewDataSource.swift index 2c23fbed7c..c786375cd0 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsTableViewDataSource.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsTableViewDataSource.swift @@ -1,4 +1,3 @@ - import Photos import UIKit diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsTableViewDelegate.swift b/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsTableViewDelegate.swift index b057223652..daced56d49 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsTableViewDelegate.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/AlbumsTableViewDelegate.swift @@ -1,4 +1,3 @@ - import UIKit final class AlbumsTableViewDelegate: NSObject, UITableViewDelegate { diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/Cell/AlbumTableViewCell.swift b/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/Cell/AlbumTableViewCell.swift index 905013d311..62d3b6999f 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/Cell/AlbumTableViewCell.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/Cell/AlbumTableViewCell.swift @@ -1,4 +1,3 @@ - import Photos import UIKit diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/Controller/AlbumsTableViewController.swift b/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/Controller/AlbumsTableViewController.swift index 92f190cff7..66a1eb84c5 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/Controller/AlbumsTableViewController.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoAlbums/Controller/AlbumsTableViewController.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGAPermissions import MEGAUIKit import Photos diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/Cell/PhotoCarouselCell.swift b/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/Cell/PhotoCarouselCell.swift index cc09ea1843..f8ca7a4536 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/Cell/PhotoCarouselCell.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/Cell/PhotoCarouselCell.swift @@ -1,4 +1,3 @@ - import Photos import UIKit diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/Controller/PhotoCarouselViewController.swift b/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/Controller/PhotoCarouselViewController.swift index 573c037380..240b0b4904 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/Controller/PhotoCarouselViewController.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/Controller/PhotoCarouselViewController.swift @@ -1,6 +1,6 @@ - import AVKit import Combine +import MEGAL10n import Photos import UIKit @@ -166,7 +166,7 @@ final class PhotoCarouselViewController: UIViewController { let alertController = UIAlertController(title: nil, message: error.localizedDescription, preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .cancel, handler: nil)) + alertController.addAction(UIAlertAction(title: Strings.localized("ok", comment: ""), style: .cancel, handler: nil)) self?.present(alertController, animated: true, completion: nil) MEGALogError("[Photo Carousel View] unable to play video \(error.localizedDescription)") return diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/FlowLayout/PhotoCarouselFlowLayout.swift b/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/FlowLayout/PhotoCarouselFlowLayout.swift index 33cbd0e7bc..4518bb3e98 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/FlowLayout/PhotoCarouselFlowLayout.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/FlowLayout/PhotoCarouselFlowLayout.swift @@ -1,4 +1,3 @@ - import UIKit final class PhotoCarouselFlowLayout: UICollectionViewFlowLayout { diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/PhotoCarouselDataSource.swift b/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/PhotoCarouselDataSource.swift index 74a339aa21..d35985c553 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/PhotoCarouselDataSource.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/PhotoCarouselDataSource.swift @@ -1,4 +1,3 @@ - import Photos import UIKit diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/PhotoCarouselDelegate.swift b/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/PhotoCarouselDelegate.swift index 85ac7cc5f2..baddf4e45e 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/PhotoCarouselDelegate.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoCarousel/PhotoCarouselDelegate.swift @@ -1,4 +1,3 @@ - import UIKit final class PhotoCarouselDelegate: PhotoGridViewDelegate { diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoGridView/Cell/PhotoGridViewCell.swift b/iMEGA/Node/PhotosAppBrowser/PhotoGridView/Cell/PhotoGridViewCell.swift index 91c215f4e5..2d1339e3b1 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoGridView/Cell/PhotoGridViewCell.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoGridView/Cell/PhotoGridViewCell.swift @@ -1,4 +1,3 @@ - import Photos import UIKit diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoGridView/FlowLayout/PhotoGridViewFlowLayout.swift b/iMEGA/Node/PhotosAppBrowser/PhotoGridView/FlowLayout/PhotoGridViewFlowLayout.swift index a900387875..1d67e087e3 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoGridView/FlowLayout/PhotoGridViewFlowLayout.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoGridView/FlowLayout/PhotoGridViewFlowLayout.swift @@ -1,4 +1,3 @@ - import UIKit final class PhotoGridViewFlowLayout: UICollectionViewFlowLayout { diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoGridView/PhotoGridViewDataSource.swift b/iMEGA/Node/PhotosAppBrowser/PhotoGridView/PhotoGridViewDataSource.swift index 8f84820131..66c3fb4364 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoGridView/PhotoGridViewDataSource.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoGridView/PhotoGridViewDataSource.swift @@ -1,4 +1,3 @@ - import Photos import UIKit diff --git a/iMEGA/Node/PhotosAppBrowser/PhotoGridView/PhotoGridViewDelegate.swift b/iMEGA/Node/PhotosAppBrowser/PhotoGridView/PhotoGridViewDelegate.swift index bf74b1b093..13036ab198 100644 --- a/iMEGA/Node/PhotosAppBrowser/PhotoGridView/PhotoGridViewDelegate.swift +++ b/iMEGA/Node/PhotosAppBrowser/PhotoGridView/PhotoGridViewDelegate.swift @@ -1,4 +1,3 @@ - import UIKit class PhotoGridViewDelegate: NSObject { diff --git a/iMEGA/Node/Shared Items/SharedItemsViewController+Additions.swift b/iMEGA/Node/Shared Items/SharedItemsViewController+Additions.swift index ae99ea761f..d98f97952c 100644 --- a/iMEGA/Node/Shared Items/SharedItemsViewController+Additions.swift +++ b/iMEGA/Node/Shared Items/SharedItemsViewController+Additions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation import MEGASDKRepo @@ -111,7 +112,7 @@ extension SharedItemsViewController { return MEGASdk.shared.contact(forEmail: share.user) } - @objc func addInShareSearcBarIfNeeded() { + @objc func addInShareSearchBarIfNeeded() { let inShareSize = incomingShareList?.size.intValue ?? 0 let unverifiedInShareSize = incomingUnverifiedShareList?.size.intValue ?? 0 @@ -267,7 +268,7 @@ extension SharedItemsViewController { incomingButton?.setBadgeCount(value: badgeValue(shares.count)) - addInShareSearcBarIfNeeded() + addInShareSearchBarIfNeeded() } @objc func updateToolbarItemsIfNeeded() { @@ -325,14 +326,32 @@ extension SharedItemsViewController { @objc func configureContactNotVerifiedImageVisibility( for cell: SharedItemsTableViewCell, - with user: MEGAUser + with user: MEGAUser?, + tab: SharedItemsTab ) { - guard isContactVerificationEnabled() else { - cell.contactVerifiedImageView.isHidden = true - return + cell.contactVerifiedImageView.isHidden = !showContentVerified(for: cell, with: user, tab: tab) + } + + @objc enum SharedItemsTab: Int { + case incomingShares + case outgoingShares + case sharedLinks + } + + private func showContentVerified( + for cell: SharedItemsTableViewCell, + with user: MEGAUser?, + tab: SharedItemsTab + ) -> Bool { + guard + tab == .incomingShares, + isContactVerificationEnabled(), + let user + else { + return false } - - cell.contactVerifiedImageView.isHidden = !isContactVerificationEnabled() || !isContactVerified(user) + + return isContactVerified(user) } @objc func isContactVerified(_ user: MEGAUser) -> Bool { @@ -364,6 +383,14 @@ extension SharedItemsViewController { } return isContactVerificationEnabled() && !isUserVerified && incomingButton?.isSelected == true } + + @objc func selectedCountTitle() -> String { + guard let selectedCount = (selectedNodesMutableArray as? [MEGANode])?.count, + selectedCount > 0 else { + return Strings.Localizable.selectTitle + } + return Strings.Localizable.General.Format.itemsSelected(selectedCount) + } } // MARK: - SharedItemsTableViewCellDelegate diff --git a/iMEGA/Node/Shared Items/SharedItemsViewController+ContextMenu.swift b/iMEGA/Node/Shared Items/SharedItemsViewController+ContextMenu.swift index 76eb985d9d..4159325f34 100644 --- a/iMEGA/Node/Shared Items/SharedItemsViewController+ContextMenu.swift +++ b/iMEGA/Node/Shared Items/SharedItemsViewController+ContextMenu.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n import MEGASDKRepo extension SharedItemsViewController: DisplayMenuDelegate { diff --git a/iMEGA/Node/Shared Items/SharedItemsViewController+EmptyStates.swift b/iMEGA/Node/Shared Items/SharedItemsViewController+EmptyStates.swift index 6fa958f69c..101408e91a 100644 --- a/iMEGA/Node/Shared Items/SharedItemsViewController+EmptyStates.swift +++ b/iMEGA/Node/Shared Items/SharedItemsViewController+EmptyStates.swift @@ -1,3 +1,4 @@ +import MEGAL10n extension SharedItemsViewController: DZNEmptyDataSetSource { public func customView(forEmptyDataSet scrollView: UIScrollView) -> UIView? { diff --git a/iMEGA/Node/Shared Items/SharedItemsViewController+TabSelection.swift b/iMEGA/Node/Shared Items/SharedItemsViewController+TabSelection.swift index 99945b55ab..b6c56610c8 100644 --- a/iMEGA/Node/Shared Items/SharedItemsViewController+TabSelection.swift +++ b/iMEGA/Node/Shared Items/SharedItemsViewController+TabSelection.swift @@ -1,4 +1,3 @@ - extension SharedItemsViewController { @objc func updateTabSelection() { self.selectorView?.backgroundColor = .mnz_mainBars(for: traitCollection) diff --git a/iMEGA/Node/Shared Items/SharedItemsViewController.m b/iMEGA/Node/Shared Items/SharedItemsViewController.m index 78726c0f2b..592a7c8327 100644 --- a/iMEGA/Node/Shared Items/SharedItemsViewController.m +++ b/iMEGA/Node/Shared Items/SharedItemsViewController.m @@ -25,6 +25,8 @@ #import "EmptyStateView.h" #import "MEGAPhotoBrowserViewController.h" #import "NodeTableViewCell.h" + +@import MEGAL10nObjc; @import MEGAUIKit; @interface SharedItemsViewController () { @@ -62,14 +64,14 @@ - (void)viewDidLoad { self.tableView.allowsMultipleSelectionDuringEditing = YES; - self.navigationItem.title = NSLocalizedString(@"sharedItems", @"Title of Shared Items section"); - self.editBarButtonItem.title = NSLocalizedString(@"cancel", @"Button title to cancel something"); + self.navigationItem.title = LocalizedString(@"sharedItems", @"Title of Shared Items section"); + self.editBarButtonItem.title = LocalizedString(@"cancel", @"Button title to cancel something"); [self setNavigationBarButtons]; - [self.incomingButton setTitle:NSLocalizedString(@"incoming", nil) forState:UIControlStateNormal]; - [self.outgoingButton setTitle:NSLocalizedString(@"outgoing", nil) forState:UIControlStateNormal]; - [self.linksButton setTitle:NSLocalizedString(@"Links", nil) forState:UIControlStateNormal]; + [self.incomingButton setTitle:LocalizedString(@"incoming", @"") forState:UIControlStateNormal]; + [self.outgoingButton setTitle:LocalizedString(@"outgoing", @"") forState:UIControlStateNormal]; + [self.linksButton setTitle:LocalizedString(@"Links", @"") forState:UIControlStateNormal]; self.incomingButton.titleLabel.adjustsFontForContentSizeCategory = YES; self.outgoingButton.titleLabel.adjustsFontForContentSizeCategory = YES; @@ -257,7 +259,7 @@ - (void)incomingVerifiedNodes { [self.incomingNodesMutableArray addObject:node]; } - [self addInShareSearcBarIfNeeded]; + [self addInShareSearchBarIfNeeded]; } - (void)allOutgoingNodes { @@ -406,13 +408,9 @@ - (void)showNodeInfo:(MEGANode *)node from:(UIButton *)sender { - (void)updateNavigationBarTitle { NSString *navigationTitle; if (self.tableView.isEditing) { - if (self.selectedNodesMutableArray.count == 0) { - navigationTitle = NSLocalizedString(@"selectTitle", @"Title shown on the Camera Uploads section when the edit mode is enabled. On this mode you can select photos"); - } else { - navigationTitle = (self.selectedNodesMutableArray.count == 1) ? [NSString stringWithFormat:NSLocalizedString(@"oneItemSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected one photo"), self.selectedNodesMutableArray.count] : [NSString stringWithFormat:NSLocalizedString(@"itemsSelected", @"Title shown on the Camera Uploads section when the edit mode is enabled and you have selected more than one photo"), self.selectedNodesMutableArray.count]; - } + navigationTitle = [self selectedCountTitle]; } else { - navigationTitle = NSLocalizedString(@"sharedItems", @"Title of Shared Items section"); + navigationTitle = LocalizedString(@"sharedItems", @"Title of Shared Items section"); } self.navigationItem.title = navigationTitle; @@ -459,7 +457,7 @@ - (SharedItemsTableViewCell *)incomingSharedCellAtIndexPath:(NSIndexPath *)index [self configureSelectionForCell:cell atIndexPath:indexPath forNode:node]; [self configureAccessibilityForCell:cell]; - [self configureContactNotVerifiedImageVisibilityFor: cell with:user]; + [self configureContactNotVerifiedImageVisibilityFor:cell with:user tab:SharedItemsTabIncomingShares]; return cell; } @@ -494,7 +492,7 @@ - (SharedItemsTableViewCell *)outgoingSharedCellAtIndexPath:(NSIndexPath *)index NSMutableArray *outSharesMutableArray = node.outShares; outSharesCount = outSharesMutableArray.count; if (outSharesCount > 1) { - userName = [NSString stringWithFormat:NSLocalizedString(@"sharedWithXContacts", nil), outSharesCount]; + userName = [NSString stringWithFormat:LocalizedString(@"sharedWithXContacts", @""), outSharesCount]; } else { MEGAUser *user = [MEGASdkManager.sharedMEGASdk contactForEmail:[outSharesMutableArray.firstObject user]]; NSString *userDisplayName = user.mnz_displayName; @@ -509,6 +507,7 @@ - (SharedItemsTableViewCell *)outgoingSharedCellAtIndexPath:(NSIndexPath *)index [self configureSelectionForCell:cell atIndexPath:indexPath forNode:node]; [self configureAccessibilityForCell:cell]; + [self configureContactNotVerifiedImageVisibilityFor:cell with:nil tab:SharedItemsTabOutgoingShares]; return cell; } @@ -754,10 +753,10 @@ - (IBAction)copyAction:(UIBarButtonItem *)sender { - (IBAction)leaveShareAction:(UIBarButtonItem *)sender { if ([MEGAReachabilityManager isReachableHUDIfNot]) { - NSString *alertMessage = (_selectedNodesMutableArray.count > 1) ? NSLocalizedString(@"leaveSharesAlertMessage", @"Alert message shown when the user tap on the leave share action selecting multipe inshares") : NSLocalizedString(@"leaveShareAlertMessage", @"Alert message shown when the user tap on the leave share action for one inshare"); - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"leaveFolder", nil) message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + NSString *alertMessage = (_selectedNodesMutableArray.count > 1) ? LocalizedString(@"leaveSharesAlertMessage", @"Alert message shown when the user tap on the leave share action selecting multipe inshares") : LocalizedString(@"leaveShareAlertMessage", @"Alert message shown when the user tap on the leave share action for one inshare"); + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"leaveFolder", @"") message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self removeSelectedIncomingShares]; }]]; [self presentViewController:alertController animated:YES completion:nil]; @@ -789,16 +788,16 @@ - (IBAction)removeShareAction:(UIBarButtonItem *)sender { NSString *alertMessage; if ((usersMutableArray.count == 1) && (self.selectedNodesMutableArray.count == 1)) { - alertMessage = NSLocalizedString(@"removeOneShareOneContactMessage", nil); + alertMessage = LocalizedString(@"removeOneShareOneContactMessage", @""); } else if ((usersMutableArray.count > 1) && (self.selectedNodesMutableArray.count == 1)) { - alertMessage = [NSString stringWithFormat:NSLocalizedString(@"removeOneShareMultipleContactsMessage", nil), usersMutableArray.count]; + alertMessage = [NSString stringWithFormat:LocalizedString(@"removeOneShareMultipleContactsMessage", @""), usersMutableArray.count]; } else { - alertMessage = [NSString stringWithFormat:NSLocalizedString(@"removeMultipleSharesMultipleContactsMessage", nil), usersMutableArray.count]; + alertMessage = [NSString stringWithFormat:LocalizedString(@"removeMultipleSharesMultipleContactsMessage", @""), usersMutableArray.count]; } - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"removeSharing", nil) message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"removeSharing", @"") message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self removeSelectedOutgoingShares]; }]]; [self presentViewController:alertController animated:YES completion:nil]; @@ -1202,16 +1201,7 @@ - (void)nodeAction:(NodeActionViewController *)nodeAction didSelect:(MegaNodeAct break; case MegaNodeActionTypeFavourite: { - MEGAGenericRequestDelegate *delegate = [MEGAGenericRequestDelegate.alloc initWithCompletion:^(MEGARequest * _Nonnull request, MEGAError * _Nonnull error) { - if (error.type == MEGAErrorTypeApiOk) { - if (request.numDetails == 1) { - [[QuickAccessWidgetManager.alloc init] insertFavouriteItemFor:node]; - } else { - [[QuickAccessWidgetManager.alloc init] deleteFavouriteItemFor:node]; - } - } - }]; - [MEGASdkManager.sharedMEGASdk setNodeFavourite:node favourite:!node.isFavourite delegate:delegate]; + [MEGASdk.shared setNodeFavourite:node favourite:!node.isFavourite]; break; } diff --git a/iMEGA/Node/Transfers/CancellableTransfers/CancellableTransferControllerWrapper.swift b/iMEGA/Node/Transfers/CancellableTransfers/CancellableTransferControllerWrapper.swift index 2ce1d41906..1f37f324aa 100644 --- a/iMEGA/Node/Transfers/CancellableTransfers/CancellableTransferControllerWrapper.swift +++ b/iMEGA/Node/Transfers/CancellableTransfers/CancellableTransferControllerWrapper.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGAPresentation import UIKit diff --git a/iMEGA/Node/Transfers/CancellableTransfers/CancellableTransferViewModel.swift b/iMEGA/Node/Transfers/CancellableTransfers/CancellableTransferViewModel.swift index ca4e2d4bdc..2a7bc53bac 100644 --- a/iMEGA/Node/Transfers/CancellableTransfers/CancellableTransferViewModel.swift +++ b/iMEGA/Node/Transfers/CancellableTransfers/CancellableTransferViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import MEGAPresentation protocol TransferWidgetRouting: Routing { diff --git a/iMEGA/Node/Transfers/CancellableTransfers/ViewEntity/CancellableTransferType.swift b/iMEGA/Node/Transfers/CancellableTransfers/ViewEntity/CancellableTransferType.swift index 5e3b34fb50..8a179562cb 100644 --- a/iMEGA/Node/Transfers/CancellableTransfers/ViewEntity/CancellableTransferType.swift +++ b/iMEGA/Node/Transfers/CancellableTransfers/ViewEntity/CancellableTransferType.swift @@ -1,4 +1,3 @@ - @objc enum CancellableTransferType: Int { case download case upload diff --git a/iMEGA/Node/Transfers/CancellableTransfers/ViewEntity/TransferStage.swift b/iMEGA/Node/Transfers/CancellableTransfers/ViewEntity/TransferStage.swift index 13a748c046..55867b96a9 100644 --- a/iMEGA/Node/Transfers/CancellableTransfers/ViewEntity/TransferStage.swift +++ b/iMEGA/Node/Transfers/CancellableTransfers/ViewEntity/TransferStage.swift @@ -1,4 +1,3 @@ - enum TransferStage: Int { case scanning case creatingFolderStructure diff --git a/iMEGA/Node/Transfers/TransferTableViewCell.m b/iMEGA/Node/Transfers/TransferTableViewCell.m index 99c63f1a31..b6bc9de941 100644 --- a/iMEGA/Node/Transfers/TransferTableViewCell.m +++ b/iMEGA/Node/Transfers/TransferTableViewCell.m @@ -12,6 +12,8 @@ #import "UIImage+MNZCategory.h" #import "UIImageView+MNZCategory.h" +@import MEGAL10nObjc; + @interface TransferTableViewCell () @property (weak, nonatomic) IBOutlet UIImageView *iconImageView; @@ -196,7 +198,7 @@ - (void)updateTransferIfNewState:(MEGATransfer *)transfer { - (void)configureCellWithTransferState:(MEGATransferState)transferState { if (self.overquota && self.transfer.type == MEGATransferTypeDownload) { self.arrowImageView.image = (self.transfer.type == MEGATransferTypeDownload) ? UIImage.mnz_downloadingOverquotaTransferImage : UIImage.mnz_uploadingOverquotaTransferImage; - self.infoLabel.text = NSLocalizedString(@"Transfer over quota", @"Label indicating transfer over quota"); + self.infoLabel.text = LocalizedString(@"Transfer over quota", @"Label indicating transfer over quota"); self.infoLabel.textColor = [UIColor mnz_fromHexString:@"FFCC00"]; self.pauseButton.hidden = self.cancelButton.hidden = NO; return; @@ -205,7 +207,7 @@ - (void)configureCellWithTransferState:(MEGATransferState)transferState { case MEGATransferStateQueued: { self.arrowImageView.image = (self.transfer.type == MEGATransferTypeDownload) ? UIImage.mnz_downloadQueuedTransferImage : UIImage.mnz_uploadQueuedTransferImage; self.infoLabel.textColor = [UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]; - self.infoLabel.text = NSLocalizedString(@"queued", @"Queued"); + self.infoLabel.text = LocalizedString(@"queued", @"Queued"); [self.pauseButton setImage:[UIImage imageNamed:@"pauseTransfers"] forState:UIControlStateNormal]; self.pauseButton.hidden = self.cancelButton.hidden = NO; break; @@ -218,7 +220,7 @@ - (void)configureCellWithTransferState:(MEGATransferState)transferState { self.pauseButton.hidden = self.cancelButton.hidden = NO; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"TransfersPaused"]) { self.arrowImageView.image = (self.transfer.type == MEGATransferTypeDownload) ? UIImage.mnz_downloadQueuedTransferImage : UIImage.mnz_uploadQueuedTransferImage; - NSAttributedString *status = [NSAttributedString.alloc initWithString:NSLocalizedString(@"paused", @"Paused") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; + NSAttributedString *status = [NSAttributedString.alloc initWithString:LocalizedString(@"paused", @"Paused") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; NSMutableAttributedString *infoLabel = [self transferInfoAttributedString]; [infoLabel appendAttributedString:status]; self.infoLabel.attributedText = infoLabel; @@ -231,7 +233,7 @@ - (void)configureCellWithTransferState:(MEGATransferState)transferState { case MEGATransferStatePaused: { self.arrowImageView.image = (self.transfer.type == MEGATransferTypeDownload) ? UIImage.mnz_downloadQueuedTransferImage : UIImage.mnz_uploadQueuedTransferImage; - NSAttributedString *status = [NSAttributedString.alloc initWithString:NSLocalizedString(@"paused", @"Paused") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; + NSAttributedString *status = [NSAttributedString.alloc initWithString:LocalizedString(@"paused", @"Paused") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; NSMutableAttributedString *infoLabel = [self transferInfoAttributedString]; [infoLabel appendAttributedString:status]; @@ -246,9 +248,9 @@ - (void)configureCellWithTransferState:(MEGATransferState)transferState { NSAttributedString *status; if (self.transfer.type == MEGATransferTypeUpload && MEGASdkManager.sharedMEGASdk.isStorageOverquota) { - status = [NSAttributedString.alloc initWithString:NSLocalizedString(@"transfer.storage.quotaExceeded", nil) attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; + status = [NSAttributedString.alloc initWithString:LocalizedString(@"transfer.storage.quotaExceeded", @"") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; } else { - status = [NSAttributedString.alloc initWithString:NSLocalizedString(@"Retrying...", @"Label for the state of a transfer when is being retrying - (String as short as possible).") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; + status = [NSAttributedString.alloc initWithString:LocalizedString(@"Retrying...", @"Label for the state of a transfer when is being retrying - (String as short as possible).") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; } NSMutableAttributedString *infoLabel = [self transferInfoAttributedString]; [infoLabel appendAttributedString:status]; @@ -258,7 +260,7 @@ - (void)configureCellWithTransferState:(MEGATransferState)transferState { } case MEGATransferStateCompleting: { - NSAttributedString *status = [NSAttributedString.alloc initWithString:NSLocalizedString(@"Completing...", @"Label for the state of a transfer when is being completing - (String as short as possible).") + NSAttributedString *status = [NSAttributedString.alloc initWithString:LocalizedString(@"Completing...", @"Label for the state of a transfer when is being completing - (String as short as possible).") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:(self.transfer.type == MEGATransferTypeDownload) ? UIColor.systemGreenColor : [UIColor mnz_blueForTraitCollection:self.traitCollection]}]; NSMutableAttributedString *infoLabel = [self transferInfoAttributedString]; [infoLabel appendAttributedString:status]; @@ -269,7 +271,7 @@ - (void)configureCellWithTransferState:(MEGATransferState)transferState { case MEGATransferStateCancelled: { self.arrowImageView.image = (self.transfer.type == MEGATransferTypeDownload) ? UIImage.mnz_downloadQueuedTransferImage : UIImage.mnz_uploadQueuedTransferImage; - NSAttributedString *status = [NSAttributedString.alloc initWithString:NSLocalizedString(@"Cancelled", @"Possible state of a transfer. When the transfer was cancelled") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; + NSAttributedString *status = [NSAttributedString.alloc initWithString:LocalizedString(@"Cancelled", @"Possible state of a transfer. When the transfer was cancelled") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; NSMutableAttributedString *infoLabel = [self transferInfoAttributedString]; [infoLabel appendAttributedString:status]; self.infoLabel.attributedText = infoLabel; @@ -281,21 +283,21 @@ - (void)configureCellWithTransferState:(MEGATransferState)transferState { case MEGATransferStateFailed: { self.arrowImageView.image = UIImage.mnz_errorTransferImage; self.infoLabel.textColor = [UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]; - NSString *transferFailed = NSLocalizedString(@"Transfer failed:", @"Notification message shown when a transfer failed. Keep colon."); + NSString *transferFailed = LocalizedString(@"Transfer failed:", @"Notification message shown when a transfer failed. Keep colon."); NSString *errorString; if (self.transfer.isForeignOverquota) { - errorString = NSLocalizedString(@"transfer.cell.shareOwnerStorageQuota.infoLabel", @"A message shown when uploading to an incoming share and the owner’s account is over its storage quota."); + errorString = LocalizedString(@"transfer.cell.shareOwnerStorageQuota.infoLabel", @"A message shown when uploading to an incoming share and the owner’s account is over its storage quota."); } else { MEGAError *error = self.transfer.lastErrorExtended; if (error.type == MEGAErrorTypeApiEBlocked && self.transfer.type != MEGATransferTypeUpload) { - errorString = NSLocalizedString(@"transfer.error.termsOfServiceViolation", @"Error shown when downloading a file that has violated Terms of Service."); + errorString = LocalizedString(@"transfer.error.termsOfServiceViolation", @"Error shown when downloading a file that has violated Terms of Service."); } else { errorString = [MEGAError errorStringWithErrorCode:error.type context:(self.transfer.type == MEGATransferTypeUpload) ? MEGAErrorContextUpload : MEGAErrorContextDownload]; } } - NSAttributedString *status = [NSAttributedString.alloc initWithString:[NSString stringWithFormat:@"%@ %@", transferFailed, NSLocalizedString(errorString, nil)] attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; + NSAttributedString *status = [NSAttributedString.alloc initWithString:[NSString stringWithFormat:@"%@ %@", transferFailed, LocalizedString(errorString, @"")] attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; NSMutableAttributedString *infoLabel = [self transferInfoAttributedString]; [infoLabel appendAttributedString:status]; self.infoLabel.attributedText = infoLabel; @@ -308,7 +310,7 @@ - (void)configureCellWithTransferState:(MEGATransferState)transferState { break; default: { self.arrowImageView.image = (self.transfer.type == MEGATransferTypeDownload) ? UIImage.mnz_downloadQueuedTransferImage : UIImage.mnz_uploadQueuedTransferImage; - NSAttributedString *status = [NSAttributedString.alloc initWithString:NSLocalizedString(@"queued", @"Queued") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; + NSAttributedString *status = [NSAttributedString.alloc initWithString:LocalizedString(@"queued", @"Queued") attributes:@{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1], NSForegroundColorAttributeName:[UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]}]; NSMutableAttributedString *infoLabel = [self transferInfoAttributedString]; [infoLabel appendAttributedString:status]; self.infoLabel.attributedText = infoLabel; @@ -322,7 +324,7 @@ - (void)configureCellWithTransferState:(MEGATransferState)transferState { - (void)queuedStateLayout { self.arrowImageView.image = UIImage.mnz_uploadQueuedTransferImage; self.infoLabel.textColor = [UIColor mnz_primaryGrayForTraitCollection:self.traitCollection]; - self.infoLabel.text = NSLocalizedString(@"pending", @"Label shown when a contact request is pending"); + self.infoLabel.text = LocalizedString(@"pending", @"Label shown when a contact request is pending"); self.pauseButton.hidden = YES; self.cancelButton.hidden = NO; self.progressView.progress = 0; diff --git a/iMEGA/Node/Transfers/TransfersWidgetViewController+Additions.swift b/iMEGA/Node/Transfers/TransfersWidgetViewController+Additions.swift index 94b88b7905..662b23f755 100644 --- a/iMEGA/Node/Transfers/TransfersWidgetViewController+Additions.swift +++ b/iMEGA/Node/Transfers/TransfersWidgetViewController+Additions.swift @@ -1,14 +1,19 @@ import Foundation +import MEGAL10n import UIKit -extension TransfersWidgetViewController { +extension TransfersWidgetViewController: TransferWidgetResponderProtocol { + private enum Constants { + static let defaultBottomAnchor: CGFloat = -60 + } + @objc func configProgressIndicator() { let progressIndicatorView = ProgressIndicatorView.init(frame: CGRect(x: 0, y: 0, width: 70, height: 70)) progressIndicatorView.isUserInteractionEnabled = true progressIndicatorView.isHidden = true - TransfersWidgetViewController.sharedTransfer().progressView = progressIndicatorView + self.progressView = progressIndicatorView progressIndicatorView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapProgressView))) @@ -20,7 +25,7 @@ extension TransfersWidgetViewController { guard let window = UIApplication.shared.keyWindow else { return } - showProgress(view: window, bottomAnchor: -60) + showProgress(view: window, bottomAnchor: Constants.defaultBottomAnchor) } @objc @@ -32,16 +37,19 @@ extension TransfersWidgetViewController { } window.bringSubviewToFront(progressIndicatorView) } - + @objc - func showProgress(view: UIView, bottomAnchor: Int) { + func showProgress(view: UIView, bottomAnchor: CGFloat) { guard let progressIndicatorView = TransfersWidgetViewController.sharedTransfer().progressView else { return } view.addSubview(progressIndicatorView) + NSLayoutConstraint.deactivate([progressViewBottomConstraint, progressViewWidthConstraint, progressViewHeightConstraint, progressViewLeadingConstraint, progressViewTraillingConstraint]) + progressViewWidthConstraint = progressIndicatorView.widthAnchor.constraint(equalToConstant: 70.0) progressViewHeightConstraint = progressIndicatorView.heightAnchor.constraint(equalToConstant: 70) - progressViewBottomConstraint = progressIndicatorView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: CGFloat(bottomAnchor)) + + progressViewBottomConstraint = progressIndicatorView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: bottomAnchor) progressViewLeadingConstraint = progressIndicatorView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 4.0) progressViewTraillingConstraint = progressIndicatorView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -4.0) @@ -56,6 +64,11 @@ extension TransfersWidgetViewController { NSLayoutConstraint.activate([progressViewWidthConstraint, progressViewHeightConstraint, progressViewBottomConstraint, transferWidgetSideConstraint]) } + @objc + func showWidgetIfNeeded() { + progressView?.showWidgetIfNeeded() + } + @objc func resetToKeyWindow() { setProgressViewInKeyWindow() @@ -144,3 +157,14 @@ extension TransfersWidgetViewController { self.tableView?.register(nib, forCellReuseIdentifier: identifier) } } + +protocol TransferWidgetResponderProtocol: AnyObject { + + func setProgressViewInKeyWindow() + + func bringProgressToFrontKeyWindowIfNeeded() + + func updateProgressView(bottomConstant: CGFloat) + + func showWidgetIfNeeded() +} diff --git a/iMEGA/Node/Transfers/TransfersWidgetViewController.m b/iMEGA/Node/Transfers/TransfersWidgetViewController.m index 7212ee1ff6..eb4032fa6b 100644 --- a/iMEGA/Node/Transfers/TransfersWidgetViewController.m +++ b/iMEGA/Node/Transfers/TransfersWidgetViewController.m @@ -27,6 +27,8 @@ #import "MEGA-Swift.h" #import "NSArray+MNZCategory.h" +@import MEGAL10nObjc; + @interface TransfersWidgetViewController () @property (weak, nonatomic) IBOutlet UIView *selectorView; @@ -68,6 +70,11 @@ + (instancetype)sharedTransferViewController { return instance; } +- (instancetype)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + [self configProgressIndicator]; + return self; +} - (void)viewDidLoad { [super viewDidLoad]; @@ -82,12 +89,12 @@ - (void)viewDidLoad { [self registerNibWithName:@"TransferTableViewCell" identifier:@"transferCell"]; [self registerNibWithName:@"TransferNodeTableViewCell" identifier:@"transferNodeCell"]; - self.editBarButtonItem = [UIBarButtonItem.alloc initWithTitle:NSLocalizedString(@"edit", @"Caption of a button to edit the files that are selected") style:UIBarButtonItemStylePlain target:self action:@selector(switchEdit)]; + self.editBarButtonItem = [UIBarButtonItem.alloc initWithTitle:LocalizedString(@"edit", @"Caption of a button to edit the files that are selected") style:UIBarButtonItemStylePlain target:self action:@selector(switchEdit)]; self.navigationItem.rightBarButtonItems = @[self.editBarButtonItem]; - [self.inProgressButton setTitle:NSLocalizedString(@"In Progress", @"Title of one of the filters in the Transfers section. In this case In Progress transfers") forState:UIControlStateNormal]; - [self.completedButton setTitle:NSLocalizedString(@"Completed",@"Title of one of the filters in the Transfers section. In this case Completed transfers") forState:UIControlStateNormal]; - [self.clearAllButton setTitle:self.tableView.isEditing ? NSLocalizedString(@"Clear Selected", @"tool bar title used in transfer widget, allow user to clear the selected items in the list") : NSLocalizedString(@"Clear All", @"tool bar title used in transfer widget, allow user to clear all items in the list")]; + [self.inProgressButton setTitle:LocalizedString(@"In Progress", @"Title of one of the filters in the Transfers section. In this case In Progress transfers") forState:UIControlStateNormal]; + [self.completedButton setTitle:LocalizedString(@"Completed",@"Title of one of the filters in the Transfers section. In this case Completed transfers") forState:UIControlStateNormal]; + [self.clearAllButton setTitle:self.tableView.isEditing ? LocalizedString(@"Clear Selected", @"tool bar title used in transfer widget, allow user to clear the selected items in the list") : LocalizedString(@"Clear All", @"tool bar title used in transfer widget, allow user to clear all items in the list")]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(internetConnectionChanged) name:kReachabilityChangedNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleCoreDataChangeNotification:) name:NSManagedObjectContextObjectsDidChangeNotification object:nil]; @@ -106,7 +113,7 @@ - (void)viewDidLoad { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - self.navigationItem.title = NSLocalizedString(@"transfers", @"Transfers"); + self.navigationItem.title = LocalizedString(@"transfers", @"Transfers"); [self setNavigationBarButtonItemsEnabled:[MEGAReachabilityManager isReachable]]; @@ -157,12 +164,12 @@ - (void)updateSelector { switch (self.transfersSelected) { case TransfersWidgetSelectedAll: - [self.clearAllButton setTitle:self.areTransfersPaused ? NSLocalizedString(@"Resume All", @"tool bar title used in transfer widget, allow user to resume all transfers in the list") : NSLocalizedString(@"Pause All", @"tool bar title used in transfer widget, allow user to Pause all transfers in the list")]; + [self.clearAllButton setTitle:self.areTransfersPaused ? LocalizedString(@"Resume All", @"tool bar title used in transfer widget, allow user to resume all transfers in the list") : LocalizedString(@"Pause All", @"tool bar title used in transfer widget, allow user to Pause all transfers in the list")]; self.clearAllButton.enabled = true; break; case TransfersWidgetSelectedCompleted: - [self.clearAllButton setTitle:self.tableView.isEditing ? NSLocalizedString(@"Clear Selected", @"tool bar title used in transfer widget, allow user to clear the selected items in the list") : NSLocalizedString(@"Clear All", @"tool bar title used in transfer widget, allow user to clear all items in the list")]; + [self.clearAllButton setTitle:self.tableView.isEditing ? LocalizedString(@"Clear Selected", @"tool bar title used in transfer widget, allow user to clear the selected items in the list") : LocalizedString(@"Clear All", @"tool bar title used in transfer widget, allow user to clear all items in the list")]; break; } } @@ -297,7 +304,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } - (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath { - return NSLocalizedString(@"clear", @"Button title to clear something"); + return LocalizedString(@"clear", @"Button title to clear something"); } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { @@ -689,13 +696,13 @@ - (IBAction)selectTransfersTouchUpInside:(UIButton *)sender { case TransfersWidgetSelectedAll: self.completedButton.selected = NO; self.inProgressButton.selected = YES; - [self.clearAllButton setTitle:self.areTransfersPaused ? NSLocalizedString(@"Resume All", @"tool bar title used in transfer widget, allow user to resume all transfers in the list") : NSLocalizedString(@"Pause All", @"tool bar title used in transfer widget, allow user to Pause all transfers in the list")]; + [self.clearAllButton setTitle:self.areTransfersPaused ? LocalizedString(@"Resume All", @"tool bar title used in transfer widget, allow user to resume all transfers in the list") : LocalizedString(@"Pause All", @"tool bar title used in transfer widget, allow user to Pause all transfers in the list")]; break; case TransfersWidgetSelectedCompleted: self.inProgressButton.selected = NO; self.completedButton.selected = YES; - [self.clearAllButton setTitle:self.tableView.isEditing ? NSLocalizedString(@"Clear Selected", @"tool bar title used in transfer widget, allow user to clear the selected items in the list") : NSLocalizedString(@"Clear All", @"tool bar title used in transfer widget, allow user to clear all items in the list")]; + [self.clearAllButton setTitle:self.tableView.isEditing ? LocalizedString(@"Clear Selected", @"tool bar title used in transfer widget, allow user to clear the selected items in the list") : LocalizedString(@"Clear All", @"tool bar title used in transfer widget, allow user to clear all items in the list")]; break; } @@ -760,11 +767,11 @@ - (IBAction)cancelTransfersAction:(UIBarButtonItem *)sender { if ((self.transfers.count == 0) && (self.uploadTransfersQueued.count == 0)) { return; } - NSString *transfersTypeString = NSLocalizedString(@"allInUppercaseTransfers", @"ALL transfers"); + NSString *transfersTypeString = LocalizedString(@"allInUppercaseTransfers", @"ALL transfers"); - UIAlertController *cancelTransfersAlert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"cancelTransfersTitle", @"Cancel transfers") message:[NSString stringWithFormat:NSLocalizedString(@"cancelTransfersText", @"Do you want to cancel %@?"), transfersTypeString] preferredStyle:UIAlertControllerStyleAlert]; - [cancelTransfersAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - [cancelTransfersAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertController *cancelTransfersAlert = [UIAlertController alertControllerWithTitle:LocalizedString(@"cancelTransfersTitle", @"Cancel transfers") message:[NSString stringWithFormat:LocalizedString(@"cancelTransfersText", @"Do you want to cancel %@?"), transfersTypeString] preferredStyle:UIAlertControllerStyleAlert]; + [cancelTransfersAlert addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [cancelTransfersAlert addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [self cancelTransfersForDirection:0]; [self cancelTransfersForDirection:1]; @@ -788,13 +795,13 @@ - (NSString *)titleForEmptyState { switch (self.transfersSelected) { case TransfersWidgetSelectedAll: if (self.areTransfersPaused) { - text = NSLocalizedString(@"transfersEmptyState_titlePaused", nil); + text = LocalizedString(@"transfersEmptyState_titlePaused", @""); } else { - text = NSLocalizedString(@"transfersEmptyState_titleAll", @"Title shown when the there's no transfers and they aren't paused"); + text = LocalizedString(@"transfersEmptyState_titleAll", @"Title shown when the there's no transfers and they aren't paused"); } break; case TransfersWidgetSelectedCompleted: - text = NSLocalizedString(@"transfersEmptyState_titleAll", @"Title shown when the there's no transfers and they aren't paused"); + text = LocalizedString(@"transfersEmptyState_titleAll", @"Title shown when the there's no transfers and they aren't paused"); break; } @@ -841,7 +848,7 @@ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEG case MEGARequestTypeCancelTransfers: { [self reloadView]; - [SVProgressHUD showImage:[UIImage imageNamed:@"hudMinus"] status:NSLocalizedString(@"transfersCancelled", nil)]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudMinus"] status:LocalizedString(@"transfersCancelled", @"")]; break; } @@ -910,7 +917,7 @@ - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error: } if (error.type == MEGAErrorTypeApiEIncomplete && [self shouldShowTransferCancelledMessageFor:transfer]) { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudMinus"] status:NSLocalizedString(@"transferCancelled", nil)]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudMinus"] status:LocalizedString(@"transferCancelled", @"")]; } [self deleteUploadingTransfer:transfer]; @@ -931,7 +938,7 @@ - (void)pauseTransfer:(MEGATransfer *)transfer { - (void)cancelQueuedUploadTransfer:(NSString *)localIdentifier { NSIndexPath *indexPath = [self indexPathForUploadTransferQueuedWithLocalIdentifier:localIdentifier]; if (localIdentifier && indexPath) { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudMinus"] status:NSLocalizedString(@"transferCancelled", nil)]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudMinus"] status:LocalizedString(@"transferCancelled", @"")]; [self.uploadTransfersQueued removeObjectAtIndex:indexPath.row]; [[MEGAStore shareInstance] deleteUploadTransferWithLocalIdentifier:localIdentifier]; @@ -1021,7 +1028,7 @@ - (void)updateRightBarButtonItems { - (void)switchEdit { BOOL isEditing = [self isEditing]; [self.tableView setEditing:isEditing animated:YES]; - [self.editBarButtonItem setTitle:self.tableView.isEditing ? NSLocalizedString(@"done", @"") : NSLocalizedString(@"edit", @"Caption of a button to edit the files that are selected")]; + [self.editBarButtonItem setTitle:self.tableView.isEditing ? LocalizedString(@"done", @"") : LocalizedString(@"edit", @"Caption of a button to edit the files that are selected")]; [self updateRightBarButtonItems]; [self reloadView]; diff --git a/iMEGA/RemovalConfirmationMessageGenerator.swift b/iMEGA/RemovalConfirmationMessageGenerator.swift index 845f98e212..07208e2e0c 100644 --- a/iMEGA/RemovalConfirmationMessageGenerator.swift +++ b/iMEGA/RemovalConfirmationMessageGenerator.swift @@ -1,3 +1,5 @@ +import MEGAL10n + @objc final class RemovalConfirmationMessageGenerator: NSObject { @objc static func message(forRemovedFiles fileCount: Int, andFolders folderCount: Int) -> String { precondition(fileCount > .zero || folderCount > .zero, "If both file and folder count are zero, no files/folders have been removed. There is no need for an alert") diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationCustomOptionsView.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationCustomOptionsView.swift index c40e0de129..a677acfbd0 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationCustomOptionsView.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationCustomOptionsView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationCustomOptionsView: View { diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationFrequencyOption.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationFrequencyOption.swift index 1494da993f..8d2f214ede 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationFrequencyOption.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationFrequencyOption.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n struct ScheduleMeetingCreationFrequencyOption { let name: String diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationIntervalFooterNote.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationIntervalFooterNote.swift index 34428db448..1bf5ef1edd 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationIntervalFooterNote.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationIntervalFooterNote.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationIntervalFooterNote { diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationMonthlyCustomOptionsViewModel.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationMonthlyCustomOptionsViewModel.swift index 6d994fded6..4a318cf8dc 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationMonthlyCustomOptionsViewModel.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationMonthlyCustomOptionsViewModel.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import SwiftUI final class ScheduleMeetingCreationMonthlyCustomOptionsViewModel: ObservableObject { diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationRecurrenceOption.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationRecurrenceOption.swift index 43fef2a936..614a5e0c14 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationRecurrenceOption.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationRecurrenceOption.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n enum ScheduleMeetingCreationRecurrenceOption: Identifiable { var id: Self { self } diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationRecurrenceOptionsView.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationRecurrenceOptionsView.swift index 659759629f..f557b58b40 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationRecurrenceOptionsView.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingCreationRecurrenceOptionsView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationRecurrenceOptionsView: View { diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingEndRecurrenceOptionsView.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingEndRecurrenceOptionsView.swift index 2e951bf7b7..d7162065b7 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingEndRecurrenceOptionsView.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingEndRecurrenceOptionsView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ScheduleMeetingEndRecurrenceOptionsView: View { diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingNewViewConfiguration.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingNewViewConfiguration.swift index a4fc059239..924ac651d8 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingNewViewConfiguration.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingNewViewConfiguration.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n final class ScheduleMeetingNewViewConfiguration: ScheduleMeetingViewConfigurable { diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingPushNotifications.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingPushNotifications.swift index ad45ded5d6..2991ddb064 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingPushNotifications.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingPushNotifications.swift @@ -1,3 +1,4 @@ +import MEGAL10n struct ScheduleMeetingPushNotifications { static let startsNowCategoryIdentifier = "nz.mega.startsNowScheduledMeeting.message" diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingSelectedFrequencyDetails.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingSelectedFrequencyDetails.swift index b6c8fcb7ce..e7b12854b1 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingSelectedFrequencyDetails.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingSelectedFrequencyDetails.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n struct ScheduleMeetingSelectedFrequencyDetails { let rules: ScheduledMeetingRulesEntity diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingUpdateOccurrenceViewConfiguration.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingUpdateOccurrenceViewConfiguration.swift index c31021af32..dd3f9e0eb7 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingUpdateOccurrenceViewConfiguration.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingUpdateOccurrenceViewConfiguration.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n final class ScheduleMeetingUpdateOccurrenceViewConfiguration: ScheduleMeetingUpdateViewConfiguration { let occurrence: ScheduledMeetingOccurrenceEntity diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingUpdateViewConfiguration.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingUpdateViewConfiguration.swift index a9dfe7ed9d..cd8e6df1b6 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingUpdateViewConfiguration.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingUpdateViewConfiguration.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n class ScheduleMeetingUpdateViewConfiguration: ScheduleMeetingViewConfigurable { let scheduledMeeting: ScheduledMeetingEntity diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingView.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingView.swift index bc98c55bff..211c6822dd 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingView.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import SwiftUI struct ScheduleMeetingView: View { diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingViewController.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingViewController.swift index 2cc5551baf..fdf4b5e3d0 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingViewController.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingViewController.swift @@ -1,4 +1,5 @@ import Combine +import MEGAL10n import SwiftUI final class ScheduleMeetingViewController: UIViewController { diff --git a/iMEGA/ScheduleMeetingScene/ScheduleMeetingViewModel.swift b/iMEGA/ScheduleMeetingScene/ScheduleMeetingViewModel.swift index b8ad98bc9c..3c7ead5872 100644 --- a/iMEGA/ScheduleMeetingScene/ScheduleMeetingViewModel.swift +++ b/iMEGA/ScheduleMeetingScene/ScheduleMeetingViewModel.swift @@ -1,6 +1,7 @@ import Combine import MEGADomain import MEGAFoundation +import MEGAL10n import MEGAPresentation import SwiftUI diff --git a/iMEGA/ScheduleMeetingScene/Views/DatePickerView.swift b/iMEGA/ScheduleMeetingScene/Views/DatePickerView.swift index 1b62443cc1..d4fcb3c615 100644 --- a/iMEGA/ScheduleMeetingScene/Views/DatePickerView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/DatePickerView.swift @@ -1,4 +1,3 @@ - import SwiftUI struct DatePickerView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/DetailDisclosureView.swift b/iMEGA/ScheduleMeetingScene/Views/DetailDisclosureView.swift index d74ae91b35..a42e482c85 100644 --- a/iMEGA/ScheduleMeetingScene/Views/DetailDisclosureView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/DetailDisclosureView.swift @@ -1,4 +1,3 @@ - import SwiftUI struct DetailDisclosureView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationCustomOptionsFrequencyView.swift b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationCustomOptionsFrequencyView.swift index 83a74b5976..b13c3684e5 100644 --- a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationCustomOptionsFrequencyView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationCustomOptionsFrequencyView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationCustomOptionsFrequencyView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationCustomOptionsRepetitionRulesView.swift b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationCustomOptionsRepetitionRulesView.swift index 86ac964ca2..6609a73e0f 100644 --- a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationCustomOptionsRepetitionRulesView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationCustomOptionsRepetitionRulesView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationCustomOptionsRepetitionRulesView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationDateAndRecurrenceView.swift b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationDateAndRecurrenceView.swift index 89162b6f5b..220c88165f 100644 --- a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationDateAndRecurrenceView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationDateAndRecurrenceView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationDateAndRecurrenceView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationDescriptionView.swift b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationDescriptionView.swift index ca509fc227..e41f890c5c 100644 --- a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationDescriptionView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationDescriptionView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationDescriptionView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationInvitationView.swift b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationInvitationView.swift index e98dd6960d..af5ba0b410 100644 --- a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationInvitationView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationInvitationView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationInvitationView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationMonthlyCustomOptionsView.swift b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationMonthlyCustomOptionsView.swift index 7e36d1563a..e93947395a 100644 --- a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationMonthlyCustomOptionsView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationMonthlyCustomOptionsView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationMonthlyCustomOptionsView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationNameView.swift b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationNameView.swift index 30df45a53d..0a66638c1f 100644 --- a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationNameView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationNameView.swift @@ -1,4 +1,5 @@ - +import MEGAL10n +import MEGASwiftUI import SwiftUI struct ScheduleMeetingCreationNameView: View { @@ -9,13 +10,14 @@ struct ScheduleMeetingCreationNameView: View { var body: some View { VStack { Divider() - Group { - if #available(iOS 15.0, *) { - FocusableTextFieldView(text: $viewModel.meetingName, appearFocused: appearFocused) - } else { - TextFieldView(text: $viewModel.meetingName) - } - } + + FocusableTextFieldView( + placeholder: Strings.Localizable.Meetings.ScheduleMeeting.MeetingName.placeholder, + text: $viewModel.meetingName, + appearFocused: appearFocused, + clearButtonMode: .whileEditing + ) + .padding(.horizontal) .opacity(viewModel.shouldAllowEditingRecurrenceOption ? 1.0 : 0.3) .disabled(!viewModel.shouldAllowEditingMeetingName) diff --git a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationOpenInviteView.swift b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationOpenInviteView.swift index 758ca60364..b870c2f86e 100644 --- a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationOpenInviteView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationOpenInviteView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationOpenInviteView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationPropertiesView.swift b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationPropertiesView.swift index e65b829a25..64bdc60f09 100644 --- a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationPropertiesView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationPropertiesView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationPropertiesView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationWaitingRoomView.swift b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationWaitingRoomView.swift index 70a7208035..edfd06fcee 100644 --- a/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationWaitingRoomView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/ScheduleMeetingCreationWaitingRoomView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct ScheduleMeetingCreationWaitingRoomView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/TextDescriptionView.swift b/iMEGA/ScheduleMeetingScene/Views/TextDescriptionView.swift index ac1110064e..50134582bf 100644 --- a/iMEGA/ScheduleMeetingScene/Views/TextDescriptionView.swift +++ b/iMEGA/ScheduleMeetingScene/Views/TextDescriptionView.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import SwiftUI struct TextDescriptionView: View { diff --git a/iMEGA/ScheduleMeetingScene/Views/TextFieldView.swift b/iMEGA/ScheduleMeetingScene/Views/TextFieldView.swift deleted file mode 100644 index b350ce0fa0..0000000000 --- a/iMEGA/ScheduleMeetingScene/Views/TextFieldView.swift +++ /dev/null @@ -1,39 +0,0 @@ - -import SwiftUI - -struct TextFieldView: View { - @Binding var text: String - - var body: some View { - TextField( - Strings.Localizable.Meetings.ScheduleMeeting.MeetingName.placeholder, - text: $text - ) - .onAppear { - UITextField.appearance().clearButtonMode = .whileEditing - } - .padding(.horizontal) - } -} - -@available(iOS 15.0, *) -struct FocusableTextFieldView: View { - @Binding var text: String - @FocusState var focused: Bool - var appearFocused: Bool - - var body: some View { - TextField( - Strings.Localizable.Meetings.ScheduleMeeting.MeetingName.placeholder, - text: $text - ) - .onAppear { - UITextField.appearance().clearButtonMode = .whileEditing - } - .padding(.horizontal) - .focused($focused) - .onAppear { - focused = appearFocused - } - } -} diff --git a/iMEGA/ScheduleMeetingScene/WeekDaysInformation.swift b/iMEGA/ScheduleMeetingScene/WeekDaysInformation.swift index 40a2c7fddf..7f1f53f6bc 100644 --- a/iMEGA/ScheduleMeetingScene/WeekDaysInformation.swift +++ b/iMEGA/ScheduleMeetingScene/WeekDaysInformation.swift @@ -1,4 +1,3 @@ - struct WeekDaysInformation { // Symbols starts with week day as Monday let symbols: [String] = Calendar.current.weekdaySymbols.shifted(1) diff --git a/iMEGA/ScheduleMeetingScene/WeekNumberInformation.swift b/iMEGA/ScheduleMeetingScene/WeekNumberInformation.swift index 3177124e37..d9cf573194 100644 --- a/iMEGA/ScheduleMeetingScene/WeekNumberInformation.swift +++ b/iMEGA/ScheduleMeetingScene/WeekNumberInformation.swift @@ -1,3 +1,5 @@ +import MEGAL10n + enum WeekNumberInformation { private static let weekNumbers = [ Strings.Localizable.Meetings.ScheduleMeeting.Create.Monthly.WeekNumber.first, diff --git a/iMEGA/ScheduledMeetingOccurrencesScene/Model/OccurrenceContextMenuOption.swift b/iMEGA/ScheduledMeetingOccurrencesScene/Model/OccurrenceContextMenuOption.swift index 958cf60d3d..8758fef6b7 100644 --- a/iMEGA/ScheduledMeetingOccurrencesScene/Model/OccurrenceContextMenuOption.swift +++ b/iMEGA/ScheduledMeetingOccurrencesScene/Model/OccurrenceContextMenuOption.swift @@ -1,4 +1,3 @@ - struct OccurrenceContextMenuOption: Identifiable, Hashable { let title: String let imageName: String diff --git a/iMEGA/ScheduledMeetingOccurrencesScene/Model/ScheduleMeetingOccurence.swift b/iMEGA/ScheduledMeetingOccurrencesScene/Model/ScheduleMeetingOccurence.swift index 7cff6ba247..dad918e836 100644 --- a/iMEGA/ScheduledMeetingOccurrencesScene/Model/ScheduleMeetingOccurence.swift +++ b/iMEGA/ScheduledMeetingOccurrencesScene/Model/ScheduleMeetingOccurence.swift @@ -1,4 +1,3 @@ - struct ScheduleMeetingOccurence: Identifiable, Equatable { let id: String let date: String diff --git a/iMEGA/ScheduledMeetingOccurrencesScene/ScheduledMeetingOccurrencesViewModel.swift b/iMEGA/ScheduledMeetingOccurrencesScene/ScheduledMeetingOccurrencesViewModel.swift index 08493c577f..599587997c 100644 --- a/iMEGA/ScheduledMeetingOccurrencesScene/ScheduledMeetingOccurrencesViewModel.swift +++ b/iMEGA/ScheduledMeetingOccurrencesScene/ScheduledMeetingOccurrencesViewModel.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n import MEGAPresentation protocol ScheduledMeetingOccurrencesRouting { diff --git a/iMEGA/ScheduledMeetingOccurrencesScene/Views/SeeMoreOccurrencesView.swift b/iMEGA/ScheduledMeetingOccurrencesScene/Views/SeeMoreOccurrencesView.swift index e3e83d2557..f0d1659d68 100644 --- a/iMEGA/ScheduledMeetingOccurrencesScene/Views/SeeMoreOccurrencesView.swift +++ b/iMEGA/ScheduledMeetingOccurrencesScene/Views/SeeMoreOccurrencesView.swift @@ -1,3 +1,4 @@ +import MEGAL10n import SwiftUI struct SeeMoreOccurrencesView: View { diff --git a/iMEGA/Supporting Files/MEGA-PrefixHeader.pch b/iMEGA/Supporting Files/MEGA-PrefixHeader.pch index ebfb5f15b4..227501a34b 100644 --- a/iMEGA/Supporting Files/MEGA-PrefixHeader.pch +++ b/iMEGA/Supporting Files/MEGA-PrefixHeader.pch @@ -1,4 +1,3 @@ - #ifdef __OBJC__ #import "MEGAConstants.h" #import "MEGALogMacros.h" diff --git a/iMEGA/Utils/AppearanceManager.swift b/iMEGA/Utils/AppearanceManager.swift index 70e4bd495d..d451035342 100644 --- a/iMEGA/Utils/AppearanceManager.swift +++ b/iMEGA/Utils/AppearanceManager.swift @@ -1,4 +1,3 @@ - import Foundation import MEGASwift diff --git a/iMEGA/Utils/BackupNodesValidator/BackupNodesValidator.swift b/iMEGA/Utils/BackupNodesValidator/BackupNodesValidator.swift index 02ed258bab..8db595268e 100644 --- a/iMEGA/Utils/BackupNodesValidator/BackupNodesValidator.swift +++ b/iMEGA/Utils/BackupNodesValidator/BackupNodesValidator.swift @@ -1,5 +1,6 @@ import Combine import MEGADomain +import MEGAL10n final class BackupNodesValidator { private let backupsUseCase: any BackupsUseCaseProtocol diff --git a/iMEGA/Utils/CallActionManager.swift b/iMEGA/Utils/CallActionManager.swift index 5512e5de19..e0e52a3d24 100644 --- a/iMEGA/Utils/CallActionManager.swift +++ b/iMEGA/Utils/CallActionManager.swift @@ -2,7 +2,7 @@ import MEGADomain @objc final class CallActionManager: NSObject { @objc static let shared = CallActionManager() - private let chatSdk = MEGASdkManager.sharedMEGAChatSdk() + private let chatSdk = MEGAChatSdk.shared private var callAvailabilityListener: CallAvailabilityListener? private var chatOnlineListener: ChatOnlineListener? private var callInProgressListener: CallInProgressListener? @@ -79,6 +79,31 @@ import MEGADomain } } + func startMeetingInWaitingRoomChat(chatId: ChatIdEntity, scheduledId: UInt64, enableVideo: Bool, enableAudio: Bool, delegate: MEGAChatStartCallRequestDelegate) { + self.chatOnlineListener = ChatOnlineListener( + chatId: chatId, + sdk: chatSdk + ) { [weak self] chatId in + guard let self else { return } + chatOnlineListener = nil + MEGALogDebug("1: CallActionManager: state is online now \(MEGASdk.base64Handle(forUserHandle: chatId) ?? "-1") ") + + configureAudioSessionForStartCall(chatId: chatId) + startCallRequestDelegate = MEGAChatStartCallRequestDelegate { [weak self] error in + guard let self else { return } + if error.type == .MEGAChatErrorTypeOk { + notifyStartCallToCallKit(chatId: chatId) + MeetingNoUserJoinedUseCase(repository: MeetingNoUserJoinedRepository.sharedRepo).start(chatId: chatId) + } + delegate.completion(error) + } + guard let startCallRequestDelegate else { return } + chatSdk.setChatVideoInDevices("Front Camera") + + chatSdk.startMeeting(inWaitingRoomChat: chatId, scheduledId: scheduledId, enableVideo: enableVideo, enableAudio: enableAudio, delegate: startCallRequestDelegate) + } + } + @objc func answerCall(chatId: UInt64, enableVideo: Bool, enableAudio: Bool, delegate: MEGAChatAnswerCallRequestDelegate) { let group = DispatchGroup() diff --git a/iMEGA/Utils/Categories/AVAudioSession+MNZCategory.h b/iMEGA/Utils/Categories/AVAudioSession+MNZCategory.h index bf07a009ba..6536e75be7 100644 --- a/iMEGA/Utils/Categories/AVAudioSession+MNZCategory.h +++ b/iMEGA/Utils/Categories/AVAudioSession+MNZCategory.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN @@ -11,19 +10,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly, getter=mnz_isBluetoothAudioConnected) BOOL mnz_BluetoothAudioConnected; -/** @brief Returns YES if there is a bluetooth route available. -* -* @return YES if there is a bluetooth route available, otherwise NO. -*/ -@property (nonatomic, readonly, getter=mnz_isBluetoothAudioRouteAvailable) BOOL mnz_isBluetoothAudioRouteAvailable; - -/** @brief Check if the first object in the current route outputs match with the type used as parameter, otherwise NO. - * - * @param portType the av audio session port type to check with. - * @return YES if the first objects of the current route outputs match with the type, otherwise NO. - */ -- (BOOL)mnz_isOutputEqualToPortType:(AVAudioSessionPort)portType; - /** @brief Convert AVAudioSessionRouteChangeReason to string. * * @param reason the AVAudioSessionRouteChangeReason reason. diff --git a/iMEGA/Utils/Categories/AVAudioSession+MNZCategory.m b/iMEGA/Utils/Categories/AVAudioSession+MNZCategory.m index 19a8437353..8b5e131c1e 100644 --- a/iMEGA/Utils/Categories/AVAudioSession+MNZCategory.m +++ b/iMEGA/Utils/Categories/AVAudioSession+MNZCategory.m @@ -1,23 +1,7 @@ - #import "AVAudioSession+MNZCategory.h" @implementation AVAudioSession (MNZCategory) -- (BOOL)mnz_isOutputEqualToPortType:(AVAudioSessionPort)portType { - BOOL ret = NO; - if (AVAudioSession.sharedInstance.currentRoute.outputs.count > 0) { - AVAudioSessionPortDescription *audioSessionPortDestription = AVAudioSession.sharedInstance.currentRoute.outputs.firstObject; - if ([audioSessionPortDestription.portType isEqualToString:portType]) { - ret = YES; - } - } else { - MEGALogWarning(@"[AVAudioSession] Array of audio outputs is empty"); - } - - MEGALogDebug(@"[AVAudioSession] Is the output equal to %@? %@", portType, ret ? @"YES" : @"NO"); - return ret; -} - - (BOOL)mnz_isBluetoothAudioConnected { BOOL ret = NO; NSArray *outputs = AVAudioSession.sharedInstance.currentRoute.outputs; @@ -34,19 +18,6 @@ - (BOOL)mnz_isBluetoothAudioConnected { return ret; } -- (BOOL)mnz_isBluetoothAudioRouteAvailable { - __block BOOL isBluetoothAudioRouteAvailable = NO; - [AVAudioSession.sharedInstance.availableInputs enumerateObjectsUsingBlock:^(AVAudioSessionPortDescription *description, NSUInteger idx, BOOL * _Nonnull stop) { - if ([@[AVAudioSessionPortBluetoothA2DP, AVAudioSessionPortBluetoothLE, AVAudioSessionPortBluetoothHFP] containsObject:description.portType]) { - isBluetoothAudioRouteAvailable = YES; - *stop = YES; - } - }]; - - MEGALogDebug(@"[AVAudioSession] Is there any bluetooth audio available? %@", isBluetoothAudioRouteAvailable ? @"YES" : @"NO"); - return isBluetoothAudioRouteAvailable; -} - - (NSString *)stringForAVAudioSessionRouteChangeReason:(AVAudioSessionRouteChangeReason)reason { NSString *ret; switch (reason) { diff --git a/iMEGA/Utils/Categories/CGPoint+Additions.swift b/iMEGA/Utils/Categories/CGPoint+Additions.swift index 370e13effa..4df44350e8 100644 --- a/iMEGA/Utils/Categories/CGPoint+Additions.swift +++ b/iMEGA/Utils/Categories/CGPoint+Additions.swift @@ -1,4 +1,3 @@ - extension CGPoint { var simD2: SIMD2 { SIMD2(x: Double(x), y: Double(y)) diff --git a/iMEGA/Utils/Categories/Date+Additions.swift b/iMEGA/Utils/Categories/Date+Additions.swift index e7bd1e5f1e..82615a9653 100644 --- a/iMEGA/Utils/Categories/Date+Additions.swift +++ b/iMEGA/Utils/Categories/Date+Additions.swift @@ -1,4 +1,3 @@ - extension Date { func string(withDateFormat dateFormat: String) -> String { return MegaDataFormatter.shared.string(fromDate: self, dateFormat: dateFormat) diff --git a/iMEGA/Utils/Categories/MEGAChatCall+Additions.swift b/iMEGA/Utils/Categories/MEGAChatCall+Additions.swift index 98c560a3b1..60fbf3557a 100644 --- a/iMEGA/Utils/Categories/MEGAChatCall+Additions.swift +++ b/iMEGA/Utils/Categories/MEGAChatCall+Additions.swift @@ -1,4 +1,3 @@ - extension MEGAChatCall { var isActiveCall: Bool { switch status { diff --git a/iMEGA/Utils/Categories/MEGAChatMessage+Additions.swift b/iMEGA/Utils/Categories/MEGAChatMessage+Additions.swift index 640db74537..9ed8bde420 100644 --- a/iMEGA/Utils/Categories/MEGAChatMessage+Additions.swift +++ b/iMEGA/Utils/Categories/MEGAChatMessage+Additions.swift @@ -1,3 +1,4 @@ +import MEGAL10n extension MEGAChatMessage { diff --git a/iMEGA/Utils/Categories/MEGAChatPeerList+Additions.swift b/iMEGA/Utils/Categories/MEGAChatPeerList+Additions.swift index 89e9534f76..88697751a4 100644 --- a/iMEGA/Utils/Categories/MEGAChatPeerList+Additions.swift +++ b/iMEGA/Utils/Categories/MEGAChatPeerList+Additions.swift @@ -1,4 +1,3 @@ - extension MEGAChatPeerList { @objc class func mnz_standardPrivilegePeerList(usersArray: [MEGAUser]) -> MEGAChatPeerList { let peerList = MEGAChatPeerList() diff --git a/iMEGA/Utils/Categories/MEGAChatRoom+Additions.swift b/iMEGA/Utils/Categories/MEGAChatRoom+Additions.swift index 87aa7ac07b..580aca025c 100644 --- a/iMEGA/Utils/Categories/MEGAChatRoom+Additions.swift +++ b/iMEGA/Utils/Categories/MEGAChatRoom+Additions.swift @@ -1,6 +1,6 @@ - import Foundation import MEGAFoundation +import MEGAL10n import MEGASwift extension MEGAChatRoom { @@ -9,7 +9,7 @@ extension MEGAChatRoom { return nil } - return MEGASdkManager.sharedMEGAChatSdk().userOnlineStatus(peerHandle(at: 0)) + return MEGAChatSdk.shared.userOnlineStatus(peerHandle(at: 0)) } @objc var isOneToOne: Bool { @@ -62,8 +62,9 @@ extension MEGAChatRoom { return userName } - return MEGASdkManager.sharedMEGAChatSdk().userFullnameFromCache(byUserHandle: userHandle) + return MEGAChatSdk.shared.userFullnameFromCache(byUserHandle: userHandle) } + @objc func chatTitle() -> String { if isGroup && !hasCustomTitle && peerCount == 0 { let date = Date(timeIntervalSince1970: TimeInterval(creationTimeStamp)) @@ -78,13 +79,13 @@ extension MEGAChatRoom { var meString = Strings.Localizable.me if me { var myNameOrEmail: String? - if let myFullname = MEGASdkManager.sharedMEGAChatSdk().myFullname { + if let myFullname = MEGAChatSdk.shared.myFullname { if !myFullname.mnz_isEmpty() { myNameOrEmail = myFullname } } if myNameOrEmail == nil { - if let myEmail = MEGASdkManager.sharedMEGAChatSdk().myEmail { + if let myEmail = MEGAChatSdk.shared.myEmail { if !myEmail.mnz_isEmpty() { myNameOrEmail = myEmail } @@ -131,7 +132,7 @@ extension MEGAChatRoom { } if handlesToLoad.isNotEmpty { - MEGASdkManager.sharedMEGAChatSdk().loadUserAttributes(forChatId: chatId, usersHandles: handlesToLoad as [NSNumber]) + MEGAChatSdk.shared.loadUserAttributes(forChatId: chatId, usersHandles: handlesToLoad as [NSNumber]) } return participantsNames @@ -148,19 +149,19 @@ extension MEGAChatRoom { } } - if let firstName = MEGASdkManager.sharedMEGAChatSdk().userFirstnameFromCache(byUserHandle: userHandle) { + if let firstName = MEGAChatSdk.shared.userFirstnameFromCache(byUserHandle: userHandle) { if !firstName.mnz_isEmpty() { return firstName } } - if let lastName = MEGASdkManager.sharedMEGAChatSdk().userLastnameFromCache(byUserHandle: userHandle) { + if let lastName = MEGAChatSdk.shared.userLastnameFromCache(byUserHandle: userHandle) { if !lastName.mnz_isEmpty() { return lastName } } - if let email = MEGASdkManager.sharedMEGAChatSdk().userEmailFromCache(byUserHandle: userHandle) { + if let email = MEGAChatSdk.shared.userEmailFromCache(byUserHandle: userHandle) { if !email.mnz_isEmpty() { return email } diff --git a/iMEGA/Utils/Categories/MEGAError+MNZCategory.h b/iMEGA/Utils/Categories/MEGAError+MNZCategory.h index 12666c39e1..34341fc22a 100644 --- a/iMEGA/Utils/Categories/MEGAError+MNZCategory.h +++ b/iMEGA/Utils/Categories/MEGAError+MNZCategory.h @@ -1,4 +1,3 @@ - #import "MEGAError.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Categories/MEGAError+MNZCategory.m b/iMEGA/Utils/Categories/MEGAError+MNZCategory.m index fd82d0d017..6ec15e3af1 100644 --- a/iMEGA/Utils/Categories/MEGAError+MNZCategory.m +++ b/iMEGA/Utils/Categories/MEGAError+MNZCategory.m @@ -1,4 +1,3 @@ - #import "MEGAError+MNZCategory.h" @implementation MEGAError (MNZCategory) diff --git a/iMEGA/Utils/Categories/MEGANode+MNZCategory.h b/iMEGA/Utils/Categories/MEGANode+MNZCategory.h index 93a61c6c37..9c50e360df 100644 --- a/iMEGA/Utils/Categories/MEGANode+MNZCategory.h +++ b/iMEGA/Utils/Categories/MEGANode+MNZCategory.h @@ -1,4 +1,3 @@ - NS_ASSUME_NONNULL_BEGIN @interface MEGANode (MNZCategory) diff --git a/iMEGA/Utils/Categories/MEGANode+MNZCategory.m b/iMEGA/Utils/Categories/MEGANode+MNZCategory.m index 7a3e78eb68..29c69f3c1b 100644 --- a/iMEGA/Utils/Categories/MEGANode+MNZCategory.m +++ b/iMEGA/Utils/Categories/MEGANode+MNZCategory.m @@ -1,4 +1,3 @@ - #import "MEGANode+MNZCategory.h" #import @@ -35,6 +34,8 @@ #import "SendToViewController.h" #import "MEGAStartUploadTransferDelegate.h" +@import MEGAL10nObjc; + @implementation MEGANode (MNZCategory) - (MEGANode *)parent { @@ -204,8 +205,8 @@ - (UIViewController *)mnz_viewControllerForNodeInFolderLink:(BOOL)isFolderLink f if (self.mnz_isPlayable) { return [[AVPlayerManager shared] makePlayerControllerFor:self folderLink:isFolderLink sdk:apiForStreaming]; } else { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"fileNotSupported", @"Alert title shown when users try to stream an unsupported audio/video file") message:NSLocalizedString(@"message_fileNotSupported", @"Alert message shown when users try to stream an unsupported audio/video file") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"fileNotSupported", @"Alert title shown when users try to stream an unsupported audio/video file") message:LocalizedString(@"message_fileNotSupported", @"Alert message shown when users try to stream an unsupported audio/video file") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; return alertController; } } else { @@ -252,36 +253,36 @@ - (void)mnz_labelActionSheetInViewController:(UIViewController *)viewController UIImageView *checkmarkImageView = [UIImageView.alloc initWithImage:[UIImage imageNamed:@"turquoise_checkmark"]]; NSMutableArray *actions = NSMutableArray.new; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"Red", @"A user can mark a folder or file with its own colour, in this case “Red”.") detail:nil accessoryView:(self.label == MEGANodeLabelRed ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Red"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"Red", @"A user can mark a folder or file with its own colour, in this case “Red”.") detail:nil accessoryView:(self.label == MEGANodeLabelRed ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Red"] style:UIAlertActionStyleDefault actionHandler:^{ if (self.label != MEGANodeLabelRed) [MEGASdkManager.sharedMEGASdk setNodeLabel:self label:MEGANodeLabelRed]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"Orange", @"A user can mark a folder or file with its own colour, in this case “Orange”.") detail:nil accessoryView:(self.label == MEGANodeLabelOrange ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Orange"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"Orange", @"A user can mark a folder or file with its own colour, in this case “Orange”.") detail:nil accessoryView:(self.label == MEGANodeLabelOrange ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Orange"] style:UIAlertActionStyleDefault actionHandler:^{ if (self.label != MEGANodeLabelOrange) [MEGASdkManager.sharedMEGASdk setNodeLabel:self label:MEGANodeLabelOrange]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"Yellow", @"A user can mark a folder or file with its own colour, in this case “Yellow”.") detail:nil accessoryView:(self.label == MEGANodeLabelYellow ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Yellow"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"Yellow", @"A user can mark a folder or file with its own colour, in this case “Yellow”.") detail:nil accessoryView:(self.label == MEGANodeLabelYellow ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Yellow"] style:UIAlertActionStyleDefault actionHandler:^{ if (self.label != MEGANodeLabelYellow) [MEGASdkManager.sharedMEGASdk setNodeLabel:self label:MEGANodeLabelYellow]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"Green", @"A user can mark a folder or file with its own colour, in this case “Green”.") detail:nil accessoryView:(self.label == MEGANodeLabelGreen ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Green"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"Green", @"A user can mark a folder or file with its own colour, in this case “Green”.") detail:nil accessoryView:(self.label == MEGANodeLabelGreen ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Green"] style:UIAlertActionStyleDefault actionHandler:^{ if (self.label != MEGANodeLabelGreen) [MEGASdkManager.sharedMEGASdk setNodeLabel:self label:MEGANodeLabelGreen]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"Blue", @"A user can mark a folder or file with its own colour, in this case “Blue”.") detail:nil accessoryView:(self.label == MEGANodeLabelBlue ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Blue"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"Blue", @"A user can mark a folder or file with its own colour, in this case “Blue”.") detail:nil accessoryView:(self.label == MEGANodeLabelBlue ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Blue"] style:UIAlertActionStyleDefault actionHandler:^{ if (self.label != MEGANodeLabelBlue) [MEGASdkManager.sharedMEGASdk setNodeLabel:self label:MEGANodeLabelBlue]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"Purple", @"A user can mark a folder or file with its own colour, in this case “Purple”.") detail:nil accessoryView:(self.label == MEGANodeLabelPurple ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Purple"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"Purple", @"A user can mark a folder or file with its own colour, in this case “Purple”.") detail:nil accessoryView:(self.label == MEGANodeLabelPurple ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Purple"] style:UIAlertActionStyleDefault actionHandler:^{ if (self.label != MEGANodeLabelPurple) [MEGASdkManager.sharedMEGASdk setNodeLabel:self label:MEGANodeLabelPurple]; }]]; - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"Grey", @"A user can mark a folder or file with its own colour, in this case “Grey”.") detail:nil accessoryView:(self.label == MEGANodeLabelGrey ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Grey"] style:UIAlertActionStyleDefault actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"Grey", @"A user can mark a folder or file with its own colour, in this case “Grey”.") detail:nil accessoryView:(self.label == MEGANodeLabelGrey ? checkmarkImageView : nil) image:[UIImage imageNamed:@"Grey"] style:UIAlertActionStyleDefault actionHandler:^{ if (self.label != MEGANodeLabelGrey) [MEGASdkManager.sharedMEGASdk setNodeLabel:self label:MEGANodeLabelGrey]; }]]; if (self.label != MEGANodeLabelUnknown) { - [actions addObject:[ActionSheetAction.alloc initWithTitle:NSLocalizedString(@"Remove Label", @"Option shown on the action sheet where you can choose or change the color label of a file or folder. The 'Remove Label' only appears if you have previously selected a label") detail:nil image:[UIImage imageNamed:@"delete"] style:UIAlertActionStyleDestructive actionHandler:^{ + [actions addObject:[ActionSheetAction.alloc initWithTitle:LocalizedString(@"Remove Label", @"Option shown on the action sheet where you can choose or change the color label of a file or folder. The 'Remove Label' only appears if you have previously selected a label") detail:nil image:[UIImage imageNamed:@"delete"] style:UIAlertActionStyleDestructive actionHandler:^{ [MEGASdkManager.sharedMEGASdk resetNodeLabel:self]; }]]; } @@ -296,7 +297,7 @@ - (void)mnz_renameNodeInViewController:(UIViewController *)viewController { - (void)mnz_renameNodeInViewController:(UIViewController *)viewController completion:(void(^)(MEGARequest *request))completion { if ([MEGAReachabilityManager isReachableHUDIfNot]) { - UIAlertController *renameAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"rename", @"Title for the action that allows you to rename a file or folder") message:NSLocalizedString(@"renameNodeMessage", @"Hint text to suggest that the user have to write the new name for the file or folder") preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *renameAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"rename", @"Title for the action that allows you to rename a file or folder") message:LocalizedString(@"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.placeholder = self.name; @@ -306,9 +307,9 @@ - (void)mnz_renameNodeInViewController:(UIViewController *)viewController comple [textField addTarget:self action:@selector(textFieldChanged:) forControlEvents:UIControlEventEditingChanged]; }]; - [renameAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [renameAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - UIAlertAction *renameAlertAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"rename", @"Title for the action that allows you to rename a file or folder") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertAction *renameAlertAction = [UIAlertAction actionWithTitle:LocalizedString(@"rename", @"Title for the action that allows you to rename a file or folder") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { if ([MEGAReachabilityManager isReachableHUDIfNot]) { NSString *alertViewTextFieldText = renameAlertController.textFields.firstObject.text; MEGANode *parentNode = [[MEGASdkManager sharedMEGASdk] nodeForHandle:self.parentHandle]; @@ -321,9 +322,9 @@ - (void)mnz_renameNodeInViewController:(UIViewController *)viewController comple MEGANode *existingChildNode = [[MEGASdkManager sharedMEGASdk] childNodeForParent:parentNode name:alertViewTextFieldText type:nodeType]; if (existingChildNode) { - NSString *duplicateErrorMessage = NSLocalizedString(@"There is already a file with the same name", @"A tooltip message which shows when a file name is duplicated during renaming."); + NSString *duplicateErrorMessage = LocalizedString(@"There is already a file with the same name", @"A tooltip message which shows when a file name is duplicated during renaming."); if (self.isFolder) { - duplicateErrorMessage = NSLocalizedString(@"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."); + duplicateErrorMessage = LocalizedString(@"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."); } [SVProgressHUD showErrorWithStatus:duplicateErrorMessage]; } else if (![alertViewTextFieldText.pathExtension isEqualToString:self.name.pathExtension]) { @@ -363,17 +364,17 @@ - (void)mnz_moveToTheRubbishBinWithCompletion:(void (^)(void))completion { - (void)mnz_removeInViewController:(UIViewController *)viewController completion:(void (^)(BOOL shouldRemove))actionCompletion { if ([MEGAReachabilityManager isReachableHUDIfNot]) { - NSString *alertTitle = NSLocalizedString(@"general.menuAction.deletePermanently", @"Title for the action that allows to remove a file or folder"); + NSString *alertTitle = LocalizedString(@"general.menuAction.deletePermanently", @"Title for the action that allows to remove a file or folder"); NSString *alertMessage = [self alertMessageForRemoved:self.type]; UIAlertController *moveRemoveLeaveAlertController = [UIAlertController alertControllerWithTitle:alertTitle message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - [moveRemoveLeaveAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + [moveRemoveLeaveAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { if (actionCompletion) { actionCompletion(NO); } }]]; - [moveRemoveLeaveAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [moveRemoveLeaveAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { if ([MEGAReachabilityManager isReachableHUDIfNot]) { void (^completion)(void) = nil; if (![viewController isKindOfClass:MEGAPhotoBrowserViewController.class]) { @@ -400,18 +401,18 @@ - (void)mnz_removeInViewController:(UIViewController *)viewController completion - (void)mnz_leaveSharingInViewController:(UIViewController *)viewController completion:(void (^ _Nullable)(BOOL))completion { if ([MEGAReachabilityManager isReachableHUDIfNot]) { - NSString *alertTitle = NSLocalizedString(@"leaveFolder", @"Button title of the action that allows to leave a shared folder"); - NSString *alertMessage = NSLocalizedString(@"leaveShareAlertMessage", @"Alert message shown when the user tap on the leave share action for one inshare"); + NSString *alertTitle = LocalizedString(@"leaveFolder", @"Button title of the action that allows to leave a shared folder"); + NSString *alertMessage = LocalizedString(@"leaveShareAlertMessage", @"Alert message shown when the user tap on the leave share action for one inshare"); UIAlertController *moveRemoveLeaveAlertController = [UIAlertController alertControllerWithTitle:alertTitle message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - [moveRemoveLeaveAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + [moveRemoveLeaveAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { if (completion) { completion(NO); } }]]; - [moveRemoveLeaveAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [moveRemoveLeaveAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { if ([MEGAReachabilityManager isReachableHUDIfNot]) { void (^comp)(void) = ^{ [viewController dismissViewControllerAnimated:YES completion:nil]; @@ -440,14 +441,14 @@ - (void)mnz_removeSharingWithCompletion:(void (^ _Nullable)(BOOL))completion { [outSharesForNodeMutableArray addObject:share]; } } - NSString *alertMessage = outSharesForNodeMutableArray.count == 1 ? NSLocalizedString(@"removeOneShareOneContactMessage", nil) : [NSString stringWithFormat:NSLocalizedString(@"removeOneShareMultipleContactsMessage", nil), outSharesForNodeMutableArray.count]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"removeSharing", nil) message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + NSString *alertMessage = outSharesForNodeMutableArray.count == 1 ? LocalizedString(@"removeOneShareOneContactMessage", @"") : [NSString stringWithFormat:LocalizedString(@"removeOneShareMultipleContactsMessage", @""), outSharesForNodeMutableArray.count]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"removeSharing", @"") message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { if (completion) { completion(NO); } }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { MEGAShareRequestDelegate *shareRequestDelegate = [[MEGAShareRequestDelegate alloc] initToChangePermissionsWithNumberOfRequests:outSharesForNodeMutableArray.count completion:nil]; for (MEGAShare *share in outSharesForNodeMutableArray) { [[MEGASdkManager sharedMEGASdk] shareNode:self withEmail:share.user level:MEGAShareTypeAccessUnknown delegate:shareRequestDelegate]; @@ -764,10 +765,10 @@ - (NSString *)mnz_fileType { NSString *fileType = [fileTypesForExtension objectForKey:self.name.pathExtension]; if (fileType.length == 0) { - fileType = [NSString stringWithFormat:@"%@ %@", self.name.pathExtension, NSLocalizedString(@"chat.match.file", @"Label to desing a file matching")]; + fileType = [NSString stringWithFormat:@"%@ %@", self.name.pathExtension, LocalizedString(@"chat.match.file", @"Label to desing a file matching")]; } else { if ([fileType containsString:@"general.filetype"]) { - NSString *localizedFiletype = NSLocalizedString(fileType, nil); + NSString *localizedFiletype = LocalizedString(fileType, @""); if (localizedFiletype) { return localizedFiletype; } diff --git a/iMEGA/Utils/Categories/MEGANode+MenuActions.swift b/iMEGA/Utils/Categories/MEGANode+MenuActions.swift index 0761ad83c3..9d9b453936 100644 --- a/iMEGA/Utils/Categories/MEGANode+MenuActions.swift +++ b/iMEGA/Utils/Categories/MEGANode+MenuActions.swift @@ -1,4 +1,3 @@ - extension MEGANode { // MARK: - Import func openBrowserToImport(in viewController: UIViewController) { diff --git a/iMEGA/Utils/Categories/MEGANodeList+MNZCategory.h b/iMEGA/Utils/Categories/MEGANodeList+MNZCategory.h index bc764fadfa..94ec8cfb57 100644 --- a/iMEGA/Utils/Categories/MEGANodeList+MNZCategory.h +++ b/iMEGA/Utils/Categories/MEGANodeList+MNZCategory.h @@ -1,4 +1,3 @@ - @interface MEGANodeList (MNZCategory) - (NSArray *)mnz_numberOfFilesAndFolders; diff --git a/iMEGA/Utils/Categories/MEGANodeList+MNZCategory.m b/iMEGA/Utils/Categories/MEGANodeList+MNZCategory.m index 98c3308a50..1ff1e7e5c2 100644 --- a/iMEGA/Utils/Categories/MEGANodeList+MNZCategory.m +++ b/iMEGA/Utils/Categories/MEGANodeList+MNZCategory.m @@ -1,4 +1,3 @@ - #import "MEGANodeList+MNZCategory.h" #import "MEGASdkManager.h" #import "NSString+MNZCategory.h" diff --git a/iMEGA/Utils/Categories/MEGASdk+MNZCategory.h b/iMEGA/Utils/Categories/MEGASdk+MNZCategory.h index ae2ac87830..939642adb4 100644 --- a/iMEGA/Utils/Categories/MEGASdk+MNZCategory.h +++ b/iMEGA/Utils/Categories/MEGASdk+MNZCategory.h @@ -1,4 +1,3 @@ - NS_ASSUME_NONNULL_BEGIN @interface MEGASdk (MNZCategory) diff --git a/iMEGA/Utils/Categories/MEGASdk+MNZCategory.m b/iMEGA/Utils/Categories/MEGASdk+MNZCategory.m index d3727fbaff..42f485a469 100644 --- a/iMEGA/Utils/Categories/MEGASdk+MNZCategory.m +++ b/iMEGA/Utils/Categories/MEGASdk+MNZCategory.m @@ -1,4 +1,3 @@ - #import "MEGASdk+MNZCategory.h" #import diff --git a/iMEGA/Utils/Categories/MEGATransfer+MNZCategory.h b/iMEGA/Utils/Categories/MEGATransfer+MNZCategory.h index 7fafbef41d..ceb456ea41 100644 --- a/iMEGA/Utils/Categories/MEGATransfer+MNZCategory.h +++ b/iMEGA/Utils/Categories/MEGATransfer+MNZCategory.h @@ -1,4 +1,3 @@ - #import #import "MEGAChatMessage.h" diff --git a/iMEGA/Utils/Categories/MEGATransfer+MNZCategory.m b/iMEGA/Utils/Categories/MEGATransfer+MNZCategory.m index be0c5103fc..db859aac15 100644 --- a/iMEGA/Utils/Categories/MEGATransfer+MNZCategory.m +++ b/iMEGA/Utils/Categories/MEGATransfer+MNZCategory.m @@ -1,4 +1,3 @@ - #import "MEGATransfer+MNZCategory.h" #import #import "Helper.h" diff --git a/iMEGA/Utils/Categories/MEGAUser+Additions.swift b/iMEGA/Utils/Categories/MEGAUser+Additions.swift index 4dde2f7e50..226eab5a3d 100644 --- a/iMEGA/Utils/Categories/MEGAUser+Additions.swift +++ b/iMEGA/Utils/Categories/MEGAUser+Additions.swift @@ -1,4 +1,3 @@ - extension MEGAUser { func avatarImage(withDelegate delegate: (any MEGARequestDelegate)?) -> UIImage? { guard let base64Handle = MEGASdk.base64Handle(forHandle: handle) else { return nil } @@ -11,11 +10,11 @@ extension MEGAUser { return UIImage(contentsOfFile: filePath) } else { if let `delegate` = delegate { - MEGASdkManager.sharedMEGASdk().getAvatarUser(self, + MEGASdk.shared.getAvatarUser(self, destinationFilePath: filePath, delegate: delegate) } else { - MEGASdkManager.sharedMEGASdk().getAvatarUser(self, destinationFilePath: filePath) + MEGASdk.shared.getAvatarUser(self, destinationFilePath: filePath) } } diff --git a/iMEGA/Utils/Categories/MEGAUser+MNZCategory.h b/iMEGA/Utils/Categories/MEGAUser+MNZCategory.h index 201fb8589e..855df7786f 100644 --- a/iMEGA/Utils/Categories/MEGAUser+MNZCategory.h +++ b/iMEGA/Utils/Categories/MEGAUser+MNZCategory.h @@ -1,4 +1,3 @@ - #import "MEGAUser.h" #import diff --git a/iMEGA/Utils/Categories/MEGAUser+MNZCategory.m b/iMEGA/Utils/Categories/MEGAUser+MNZCategory.m index 7b995d73bb..91c2cfa8b5 100644 --- a/iMEGA/Utils/Categories/MEGAUser+MNZCategory.m +++ b/iMEGA/Utils/Categories/MEGAUser+MNZCategory.m @@ -1,4 +1,3 @@ - #import "MEGAUser+MNZCategory.h" #import "Helper.h" diff --git a/iMEGA/Utils/Categories/MegaNode+Display.swift b/iMEGA/Utils/Categories/MegaNode+Display.swift index 006fe806f7..0335dbcfa3 100644 --- a/iMEGA/Utils/Categories/MegaNode+Display.swift +++ b/iMEGA/Utils/Categories/MegaNode+Display.swift @@ -1,3 +1,4 @@ +import MEGAL10n extension MEGANode { diff --git a/iMEGA/Utils/Categories/NSArray+MNZCategory.m b/iMEGA/Utils/Categories/NSArray+MNZCategory.m index 5273de0f1b..2662df61e6 100644 --- a/iMEGA/Utils/Categories/NSArray+MNZCategory.m +++ b/iMEGA/Utils/Categories/NSArray+MNZCategory.m @@ -1,4 +1,3 @@ - #import "NSArray+MNZCategory.h" @implementation NSArray (MNZCategory) diff --git a/iMEGA/Utils/Categories/NSAttributedString+MNZCategory.h b/iMEGA/Utils/Categories/NSAttributedString+MNZCategory.h index e11e063078..814771cb65 100644 --- a/iMEGA/Utils/Categories/NSAttributedString+MNZCategory.h +++ b/iMEGA/Utils/Categories/NSAttributedString+MNZCategory.h @@ -1,4 +1,3 @@ - #import @interface NSAttributedString (MNZCategory) diff --git a/iMEGA/Utils/Categories/NSAttributedString+MNZCategory.m b/iMEGA/Utils/Categories/NSAttributedString+MNZCategory.m index 997ed4c51a..cab5faaed8 100644 --- a/iMEGA/Utils/Categories/NSAttributedString+MNZCategory.m +++ b/iMEGA/Utils/Categories/NSAttributedString+MNZCategory.m @@ -1,4 +1,3 @@ - #import "NSAttributedString+MNZCategory.h" #import "NSString+MNZCategory.h" diff --git a/iMEGA/Utils/Categories/NSDate+MNZCategory.h b/iMEGA/Utils/Categories/NSDate+MNZCategory.h index 536f3fc793..0da8dbd3b2 100644 --- a/iMEGA/Utils/Categories/NSDate+MNZCategory.h +++ b/iMEGA/Utils/Categories/NSDate+MNZCategory.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Categories/NSDate+MNZCategory.m b/iMEGA/Utils/Categories/NSDate+MNZCategory.m index 17bd49751a..7b1068a2b0 100644 --- a/iMEGA/Utils/Categories/NSDate+MNZCategory.m +++ b/iMEGA/Utils/Categories/NSDate+MNZCategory.m @@ -1,4 +1,3 @@ - #import "NSDate+MNZCategory.h" const NSInteger secondsInAMinute = 60; diff --git a/iMEGA/Utils/Categories/NSFileManager+MNZCategory.h b/iMEGA/Utils/Categories/NSFileManager+MNZCategory.h index 00fd809a84..fdc5c9ac2a 100644 --- a/iMEGA/Utils/Categories/NSFileManager+MNZCategory.h +++ b/iMEGA/Utils/Categories/NSFileManager+MNZCategory.h @@ -1,4 +1,3 @@ - #import @interface NSFileManager (MNZCategory) diff --git a/iMEGA/Utils/Categories/NSFileManager+MNZCategory.m b/iMEGA/Utils/Categories/NSFileManager+MNZCategory.m index 36c46c3aae..a5ae6e7915 100644 --- a/iMEGA/Utils/Categories/NSFileManager+MNZCategory.m +++ b/iMEGA/Utils/Categories/NSFileManager+MNZCategory.m @@ -1,4 +1,3 @@ - #import "NSFileManager+MNZCategory.h" @implementation NSFileManager (MNZCategory) diff --git a/iMEGA/Utils/Categories/NSString+Additions.swift b/iMEGA/Utils/Categories/NSString+Additions.swift index e308432f09..fec701b7da 100644 --- a/iMEGA/Utils/Categories/NSString+Additions.swift +++ b/iMEGA/Utils/Categories/NSString+Additions.swift @@ -1,4 +1,3 @@ - @objc extension NSString { static var byteCountFormatter = ByteCountFormatter() diff --git a/iMEGA/Utils/Categories/NSString+MNZCategory.h b/iMEGA/Utils/Categories/NSString+MNZCategory.h index 4036335528..7744c1a4e8 100644 --- a/iMEGA/Utils/Categories/NSString+MNZCategory.h +++ b/iMEGA/Utils/Categories/NSString+MNZCategory.h @@ -1,4 +1,3 @@ - @import UIKit; typedef NS_ENUM (NSInteger, MEGAChatStatus); diff --git a/iMEGA/Utils/Categories/NSString+MNZCategory.m b/iMEGA/Utils/Categories/NSString+MNZCategory.m index 0570f70e17..c08912001e 100644 --- a/iMEGA/Utils/Categories/NSString+MNZCategory.m +++ b/iMEGA/Utils/Categories/NSString+MNZCategory.m @@ -1,4 +1,3 @@ - #import "NSString+MNZCategory.h" #import #import @@ -10,6 +9,8 @@ #import "MEGAUser+MNZCategory.h" #import +@import MEGAL10nObjc; + #ifdef MNZ_SHARE_EXTENSION #import "MEGAShare-Swift.h" #elif MNZ_NOTIFICATION_EXTENSION @@ -123,43 +124,43 @@ - (NSString *_Nullable)mnz_stringBetweenString:(NSString*)start andString:(NSStr + (NSString *)mnz_stringByFiles:(NSInteger)files andFolders:(NSInteger)folders { if (files > 0 && folders > 0) { - NSString *foldersFormat = NSLocalizedString(@"general.format.count.folderAndFile.folder", @"Subtitle shown on folders that shows its folder and file content count. Two strings will be used to make the full sentence to accommodate the multiple plural need. Full sentence examples: 1 folder • 1 file, 2 folders • 2 files, etc"); + NSString *foldersFormat = LocalizedString(@"general.format.count.folderAndFile.folder", @"Subtitle shown on folders that shows its folder and file content count. Two strings will be used to make the full sentence to accommodate the multiple plural need. Full sentence examples: 1 folder • 1 file, 2 folders • 2 files, etc"); NSString *folderCount = [NSString stringWithFormat:foldersFormat, folders]; - NSString *filesFormat = NSLocalizedString(@"general.format.count.folderAndFile.file", @"Subtitle shown on folders that shows its folder and file content count. Two strings will be used to make the full sentence to accommodate the multiple plural need. Full sentence examples: 1 folder • 1 file, 2 folders • 2 files, etc"); + NSString *filesFormat = LocalizedString(@"general.format.count.folderAndFile.file", @"Subtitle shown on folders that shows its folder and file content count. Two strings will be used to make the full sentence to accommodate the multiple plural need. Full sentence examples: 1 folder • 1 file, 2 folders • 2 files, etc"); NSString *fileCount = [NSString stringWithFormat:filesFormat, files]; return [NSString stringWithFormat:@"%@ %@", folderCount, fileCount]; } if (!files && folders > 0) { - return [NSString stringWithFormat:NSLocalizedString(@"general.format.count.folder", @"Subtitle shown on folders that gives you information about its folder content count. e.g 1 folder, 2 folders"), folders]; + return [NSString stringWithFormat:LocalizedString(@"general.format.count.folder", @"Subtitle shown on folders that gives you information about its folder content count. e.g 1 folder, 2 folders"), folders]; } if (files > 0 && !folders) { - return [NSString stringWithFormat:NSLocalizedString(@"general.format.count.file", @"Subtitle shown on folders that gives you information about its file content count. e.g 1 file, 2 files"), files]; + return [NSString stringWithFormat:LocalizedString(@"general.format.count.file", @"Subtitle shown on folders that gives you information about its file content count. e.g 1 file, 2 files"), files]; } - return NSLocalizedString(@"emptyFolder", @"Title shown when a folder doesn't have any files"); + return LocalizedString(@"emptyFolder", @"Title shown when a folder doesn't have any files"); } + (NSString * _Nullable)chatStatusString:(MEGAChatStatus)onlineStatus { NSString *onlineStatusString; switch (onlineStatus) { case MEGAChatStatusOffline: - onlineStatusString = NSLocalizedString(@"offline", @"Title of the Offline section"); + onlineStatusString = LocalizedString(@"offline", @"Title of the Offline section"); break; case MEGAChatStatusAway: - onlineStatusString = NSLocalizedString(@"away", nil); + onlineStatusString = LocalizedString(@"away", @""); break; case MEGAChatStatusOnline: - onlineStatusString = NSLocalizedString(@"online", nil); + onlineStatusString = LocalizedString(@"online", @""); break; case MEGAChatStatusBusy: - onlineStatusString = NSLocalizedString(@"busy", nil); + onlineStatusString = LocalizedString(@"busy", @""); break; default: @@ -177,38 +178,38 @@ + (NSString *)mnz_stringByEndCallReason:(MEGAChatMessageEndCallReason)endCallRea case MEGAChatMessageEndCallReasonEnded: { if (isGroup) { if (duration != nil && ![duration isEqual: @0]) { - endCallReasonString = [[NSLocalizedString(@"[A]Group call ended[/A][C]. Duration: [/C]", @"When an active goup call is ended (with duration)") stringByReplacingOccurrencesOfString:@"[/C]" withString:[NSString mnz_stringFromCallDuration:duration.integerValue]] mnz_removeWebclientFormatters]; + endCallReasonString = [[LocalizedString(@"[A]Group call ended[/A][C]. Duration: [/C]", @"When an active goup call is ended (with duration)") stringByReplacingOccurrencesOfString:@"[/C]" withString:[NSString mnz_stringFromCallDuration:duration.integerValue]] mnz_removeWebclientFormatters]; } else { - endCallReasonString = NSLocalizedString(@"Group call ended", @"When an active goup call is ended"); + endCallReasonString = LocalizedString(@"Group call ended", @"When an active goup call is ended"); } } else { - endCallReasonString = [NSString stringWithFormat:@"%@ %@", NSLocalizedString(@"callEnded", @"When an active call of user A with user B had ended"), [NSString stringWithFormat:NSLocalizedString(@"duration", @"Displayed after a call had ended, where %@ is the duration of the call (1h, 10seconds, etc)"), [NSString mnz_stringFromCallDuration:duration.integerValue]]]; + endCallReasonString = [NSString stringWithFormat:@"%@ %@", LocalizedString(@"callEnded", @"When an active call of user A with user B had ended"), [NSString stringWithFormat:LocalizedString(@"duration", @"Displayed after a call had ended, where %@ is the duration of the call (1h, 10seconds, etc)"), [NSString mnz_stringFromCallDuration:duration.integerValue]]]; } break; } case MEGAChatMessageEndCallReasonRejected: - endCallReasonString = NSLocalizedString(@"callWasRejected", @"When an outgoing call of user A with user B had been rejected by user B"); + endCallReasonString = LocalizedString(@"callWasRejected", @"When an outgoing call of user A with user B had been rejected by user B"); break; case MEGAChatMessageEndCallReasonNoAnswer: if (userHandle == [MEGASdkManager sharedMEGAChatSdk].myUserHandle) { - endCallReasonString = NSLocalizedString(@"callWasNotAnswered", @"When an active call of user A with user B had not answered"); + endCallReasonString = LocalizedString(@"callWasNotAnswered", @"When an active call of user A with user B had not answered"); } else { - endCallReasonString = NSLocalizedString(@"missedCall", @"Title of the notification for a missed call"); + endCallReasonString = LocalizedString(@"missedCall", @"Title of the notification for a missed call"); } break; case MEGAChatMessageEndCallReasonFailed: - endCallReasonString = NSLocalizedString(@"callFailed", @"When an active call of user A with user B had failed"); + endCallReasonString = LocalizedString(@"callFailed", @"When an active call of user A with user B had failed"); break; case MEGAChatMessageEndCallReasonCancelled: if (userHandle == [MEGASdkManager sharedMEGAChatSdk].myUserHandle) { - endCallReasonString = NSLocalizedString(@"callWasCancelled", @"When an active call of user A with user B had cancelled"); + endCallReasonString = LocalizedString(@"callWasCancelled", @"When an active call of user A with user B had cancelled"); } else { - endCallReasonString = NSLocalizedString(@"missedCall", @"Title of the notification for a missed call"); + endCallReasonString = LocalizedString(@"missedCall", @"Title of the notification for a missed call"); } break; @@ -227,31 +228,31 @@ + (NSString *)mnz_hoursDaysWeeksMonthsOrYearFrom:(NSUInteger)seconds { NSUInteger yearModulo = seconds % secondsInAYear; if (yearModulo == 0) { - NSString *format = NSLocalizedString(@"general.format.retentionPeriod.year", @"The number of years e.g. 1 year, 5 years etc."); + NSString *format = LocalizedString(@"general.format.retentionPeriod.year", @"The number of years e.g. 1 year, 5 years etc."); return [NSString stringWithFormat:format, 1]; } if (monthsModulo == 0) { NSUInteger months = seconds / secondsInAMonth_30; - NSString *format = NSLocalizedString(@"general.format.retentionPeriod.month", @"The number of months e.g. 1 month, 5 months etc."); + NSString *format = LocalizedString(@"general.format.retentionPeriod.month", @"The number of months e.g. 1 month, 5 months etc."); return [NSString stringWithFormat:format, months]; } if (weeksModulo == 0) { NSUInteger weeks = seconds / secondsInAWeek; - NSString *format = NSLocalizedString(@"general.format.retentionPeriod.week", @"The number of weeks e.g. 1 week, 5 weeks etc."); + NSString *format = LocalizedString(@"general.format.retentionPeriod.week", @"The number of weeks e.g. 1 week, 5 weeks etc."); return [NSString stringWithFormat:format, weeks]; } if (daysModulo == 0) { NSUInteger days = seconds / secondsInADay; - NSString *format = NSLocalizedString(@"general.format.retentionPeriod.day", @"The number of days e.g. 1 day, 5 days etc."); + NSString *format = LocalizedString(@"general.format.retentionPeriod.day", @"The number of days e.g. 1 day, 5 days etc."); return [NSString stringWithFormat:format, days]; } if (hoursModulo == 0) { NSUInteger hours = seconds / secondsInAHour; - NSString *format = NSLocalizedString(@"general.format.retentionPeriod.hour", @"The number of hours e.g. 1 hour, 5 hours etc."); + NSString *format = LocalizedString(@"general.format.retentionPeriod.hour", @"The number of hours e.g. 1 hour, 5 hours etc."); return [NSString stringWithFormat:format, hours]; } @@ -261,28 +262,28 @@ + (NSString *)mnz_hoursDaysWeeksMonthsOrYearFrom:(NSUInteger)seconds { + (NSString *)localizedSortOrderType:(MEGASortOrderType)sortOrderType { switch (sortOrderType) { case MEGASortOrderTypeDefaultDesc: - return NSLocalizedString(@"nameDescending", @"Sort by option (2/6). This one arranges the files on reverse alphabethical order"); + return LocalizedString(@"nameDescending", @"Sort by option (2/6). This one arranges the files on reverse alphabethical order"); case MEGASortOrderTypeSizeDesc: - return NSLocalizedString(@"largest", @"Sort by option (3/6). This one order the files by its size, in this case from bigger to smaller size"); + return LocalizedString(@"largest", @"Sort by option (3/6). This one order the files by its size, in this case from bigger to smaller size"); case MEGASortOrderTypeSizeAsc: - return NSLocalizedString(@"smallest", @"Sort by option (4/6). This one order the files by its size, in this case from smaller to bigger size"); + return LocalizedString(@"smallest", @"Sort by option (4/6). This one order the files by its size, in this case from smaller to bigger size"); case MEGASortOrderTypeModificationDesc: - return NSLocalizedString(@"newest", @"Sort by option (5/6). This one order the files by its modification date, newer first"); + return LocalizedString(@"newest", @"Sort by option (5/6). This one order the files by its modification date, newer first"); case MEGASortOrderTypeModificationAsc: - return NSLocalizedString(@"oldest", @"Sort by option (6/6). This one order the files by its modification date, older first"); + return LocalizedString(@"oldest", @"Sort by option (6/6). This one order the files by its modification date, older first"); case MEGASortOrderTypeLabelAsc: - return NSLocalizedString(@"cloudDrive.sort.label", @"A menu item in the left panel drop down menu to allow sorting by label."); + return LocalizedString(@"cloudDrive.sort.label", @"A menu item in the left panel drop down menu to allow sorting by label."); case MEGASortOrderTypeFavouriteAsc: - return NSLocalizedString(@"Favourite", @"Context menu item. Allows user to add file/folder to favourites"); + return LocalizedString(@"Favourite", @"Context menu item. Allows user to add file/folder to favourites"); default: - return NSLocalizedString(@"nameAscending", @"Sort by option (1/6). This one orders the files alphabethically"); + return LocalizedString(@"nameAscending", @"Sort by option (1/6). This one orders the files alphabethically"); } } @@ -349,22 +350,22 @@ + (NSString *)mnz_stringFromCallDuration:(NSInteger)duration { NSInteger hours = (ti / 3600); if (hours > 0) { if (minutes == 0) { - NSString *format = NSLocalizedString(@"call.duration.hour", nil); + NSString *format = LocalizedString(@"call.duration.hour", @""); return [NSString stringWithFormat:format, hours]; } else { - NSString *hourFormat = NSLocalizedString(@"call.duration.hourAndMinute.hour", nil); + NSString *hourFormat = LocalizedString(@"call.duration.hourAndMinute.hour", @""); NSString *hourString = [NSString stringWithFormat:hourFormat, hours]; - NSString *minuteFormat = NSLocalizedString(@"call.duration.hourAndMinute.minute", nil); + NSString *minuteFormat = LocalizedString(@"call.duration.hourAndMinute.minute", @""); NSString *minuteString = [NSString stringWithFormat:minuteFormat, minutes]; return [NSString stringWithFormat:@"%@%@", hourString, minuteString]; } } else if (minutes > 0) { - NSString *format = NSLocalizedString(@"call.duration.minute", nil); + NSString *format = LocalizedString(@"call.duration.minute", @""); return [NSString stringWithFormat:format, minutes]; } else { - NSString *format = NSLocalizedString(@"call.duration.second", nil); + NSString *format = LocalizedString(@"call.duration.second", @""); return [NSString stringWithFormat:format, seconds]; } } @@ -727,13 +728,13 @@ + (NSString *)mnz_lastGreenStringFromMinutes:(NSInteger)minutes { NSString *timeString = dateLastSeen.mnz_formattedHourAndMinutes; NSString *dateString; if ([[NSCalendar currentCalendar] isDateInToday:dateLastSeen]) { - dateString = NSLocalizedString(@"Today", @""); + dateString = LocalizedString(@"Today", @""); } else if ([[NSCalendar currentCalendar] isDateInYesterday:dateLastSeen]) { - dateString = NSLocalizedString(@"Yesterday", @""); + dateString = LocalizedString(@"Yesterday", @""); } else { dateString = [dateLastSeen formattedDateWithFormat:@"dd MMM"]; } - lastSeenMessage = NSLocalizedString(@"Last seen %s", @"Shown when viewing a 1on1 chat (at least for now), if the user is offline."); + lastSeenMessage = LocalizedString(@"Last seen %s", @"Shown when viewing a 1on1 chat (at least for now), if the user is offline."); BOOL isRTLLanguage; if ([[[[NSBundle mainBundle] bundlePath] pathExtension] isEqualToString:@"appex"]) { @@ -750,7 +751,7 @@ + (NSString *)mnz_lastGreenStringFromMinutes:(NSInteger)minutes { lastSeenMessage = [lastSeenMessage stringByReplacingOccurrencesOfString:@"%s" withString:[NSString stringWithFormat:@"%@ %@", dateString, timeString]]; } } else { - lastSeenMessage = NSLocalizedString(@"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)"); + lastSeenMessage = LocalizedString(@"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; } @@ -791,9 +792,9 @@ + (NSString *)mnz_addedByInRecentActionBucket:(MEGARecentActionBucket *)recentAc } if (recentActionBucket.isUpdate) { - addebByString = [NSString stringWithFormat:NSLocalizedString(@"home.recent.modifiedByLabel", @"Label that indicates who modified a file into a recents bucket. %1 is a placeholder for a name, eg: Haley"), userNameThatMadeTheAction]; + addebByString = [NSString stringWithFormat:LocalizedString(@"home.recent.modifiedByLabel", @"Label that indicates who modified a file into a recents bucket. %1 is a placeholder for a name, eg: Haley"), userNameThatMadeTheAction]; } else { - addebByString = [NSString stringWithFormat:NSLocalizedString(@"home.recent.createdByLabel", @"Label that indicates who uploaded a file into a recents bucket. %1 is a placeholder for a name, eg: Haley"), userNameThatMadeTheAction]; + addebByString = [NSString stringWithFormat:LocalizedString(@"home.recent.createdByLabel", @"Label that indicates who uploaded a file into a recents bucket. %1 is a placeholder for a name, eg: Haley"), userNameThatMadeTheAction]; } return addebByString; diff --git a/iMEGA/Utils/Categories/NSURL+MNZCategory.h b/iMEGA/Utils/Categories/NSURL+MNZCategory.h index d26198f314..f8bef627a8 100644 --- a/iMEGA/Utils/Categories/NSURL+MNZCategory.h +++ b/iMEGA/Utils/Categories/NSURL+MNZCategory.h @@ -1,4 +1,3 @@ - #import "URLType.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Categories/NSURL+MNZCategory.m b/iMEGA/Utils/Categories/NSURL+MNZCategory.m index a7b4593fe5..357d06c944 100644 --- a/iMEGA/Utils/Categories/NSURL+MNZCategory.m +++ b/iMEGA/Utils/Categories/NSURL+MNZCategory.m @@ -1,4 +1,3 @@ - #import "NSURL+MNZCategory.h" #import @@ -10,6 +9,8 @@ #import "MEGAGenericRequestDelegate.h" #import "MEGASdkManager.h" +@import MEGAL10nObjc; + @implementation NSURL (MNZCategory) - (void)mnz_presentSafariViewController { @@ -19,7 +20,7 @@ - (void)mnz_presentSafariViewController { MEGALogInfo(@"URL opened on other app"); } else { MEGALogInfo(@"URL NOT opened"); - [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid")]; + [SVProgressHUD showErrorWithStatus:LocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid")]; } }]; return; diff --git a/iMEGA/Utils/Categories/SIMD2+Additions.swift b/iMEGA/Utils/Categories/SIMD2+Additions.swift index 7897d771ea..87caed1e5b 100644 --- a/iMEGA/Utils/Categories/SIMD2+Additions.swift +++ b/iMEGA/Utils/Categories/SIMD2+Additions.swift @@ -1,4 +1,3 @@ - extension SIMD2 where Scalar == Double { var point: CGPoint { return CGPoint(x: x, y: y) diff --git a/iMEGA/Utils/Categories/TimeInterval+Additions.swift b/iMEGA/Utils/Categories/TimeInterval+Additions.swift index 38cc08b0c6..7c87b72db1 100644 --- a/iMEGA/Utils/Categories/TimeInterval+Additions.swift +++ b/iMEGA/Utils/Categories/TimeInterval+Additions.swift @@ -1,5 +1,5 @@ - import Foundation +import MEGAL10n extension TimeInterval { var dndFormattedString: String? { diff --git a/iMEGA/Utils/Categories/UIActivityIndicatorView+Additions.swift b/iMEGA/Utils/Categories/UIActivityIndicatorView+Additions.swift index 888111f192..9d3960c876 100644 --- a/iMEGA/Utils/Categories/UIActivityIndicatorView+Additions.swift +++ b/iMEGA/Utils/Categories/UIActivityIndicatorView+Additions.swift @@ -1,4 +1,3 @@ - extension UIActivityIndicatorView { @objc class func mnz_init() -> UIActivityIndicatorView { diff --git a/iMEGA/Utils/Categories/UIAlertController+Additions.swift b/iMEGA/Utils/Categories/UIAlertController+Additions.swift index 4ae3772e8a..0653c109fa 100644 --- a/iMEGA/Utils/Categories/UIAlertController+Additions.swift +++ b/iMEGA/Utils/Categories/UIAlertController+Additions.swift @@ -1,3 +1,5 @@ +import MEGAL10n + extension UIAlertController { // MARK: - UIAlertController for interactive dismissal diff --git a/iMEGA/Utils/Categories/UIAlertController+InAppPurchase.swift b/iMEGA/Utils/Categories/UIAlertController+InAppPurchase.swift index b8b09f797f..fde2ec3ed9 100644 --- a/iMEGA/Utils/Categories/UIAlertController+InAppPurchase.swift +++ b/iMEGA/Utils/Categories/UIAlertController+InAppPurchase.swift @@ -1,4 +1,6 @@ - extension UIAlertController { +import MEGAL10n + +extension UIAlertController { @objc class func inAppPurchaseAlertWithAppStoreSettingsButton(_ alertTitle: String, alertMessage: String?) -> UIAlertController { let alertController: UIAlertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert) diff --git a/iMEGA/Utils/Categories/UIApplication+Additions.swift b/iMEGA/Utils/Categories/UIApplication+Additions.swift index a81b196718..d6c29c43a7 100644 --- a/iMEGA/Utils/Categories/UIApplication+Additions.swift +++ b/iMEGA/Utils/Categories/UIApplication+Additions.swift @@ -1,4 +1,3 @@ - import Foundation extension UIApplication { diff --git a/iMEGA/Utils/Categories/UIApplication+MNZCategory.h b/iMEGA/Utils/Categories/UIApplication+MNZCategory.h index ebe95b5d1b..648db58359 100644 --- a/iMEGA/Utils/Categories/UIApplication+MNZCategory.h +++ b/iMEGA/Utils/Categories/UIApplication+MNZCategory.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Categories/UIApplication+MNZCategory.m b/iMEGA/Utils/Categories/UIApplication+MNZCategory.m index f09c1b68be..bcc46ff2e1 100644 --- a/iMEGA/Utils/Categories/UIApplication+MNZCategory.m +++ b/iMEGA/Utils/Categories/UIApplication+MNZCategory.m @@ -1,4 +1,3 @@ - #import "UIApplication+MNZCategory.h" @implementation UIApplication (MNZCategory) diff --git a/iMEGA/Utils/Categories/UIButton+Additions.swift b/iMEGA/Utils/Categories/UIButton+Additions.swift index 6dc363a978..568cb27e76 100644 --- a/iMEGA/Utils/Categories/UIButton+Additions.swift +++ b/iMEGA/Utils/Categories/UIButton+Additions.swift @@ -1,4 +1,3 @@ - @objc enum MEGACustomButtonStyle: Int { case none case basic diff --git a/iMEGA/Utils/Categories/UICollectionView+MNZCategory.h b/iMEGA/Utils/Categories/UICollectionView+MNZCategory.h index 98eea1beff..e91ba44375 100644 --- a/iMEGA/Utils/Categories/UICollectionView+MNZCategory.h +++ b/iMEGA/Utils/Categories/UICollectionView+MNZCategory.h @@ -1,4 +1,3 @@ - #import @interface UICollectionView (MNZCategory) diff --git a/iMEGA/Utils/Categories/UICollectionView+MNZCategory.m b/iMEGA/Utils/Categories/UICollectionView+MNZCategory.m index 7859e631db..f70d1b0720 100644 --- a/iMEGA/Utils/Categories/UICollectionView+MNZCategory.m +++ b/iMEGA/Utils/Categories/UICollectionView+MNZCategory.m @@ -1,4 +1,3 @@ - #import "UICollectionView+MNZCategory.h" @implementation UICollectionView (MNZCategory) diff --git a/iMEGA/Utils/Categories/UIColor+Additions.swift b/iMEGA/Utils/Categories/UIColor+Additions.swift index 64379bdd43..70e4758044 100644 --- a/iMEGA/Utils/Categories/UIColor+Additions.swift +++ b/iMEGA/Utils/Categories/UIColor+Additions.swift @@ -1,4 +1,3 @@ - import Foundation extension UIColor { diff --git a/iMEGA/Utils/Categories/UIColor+MNZCategory.h b/iMEGA/Utils/Categories/UIColor+MNZCategory.h index 15971e46c4..0117a65b6e 100644 --- a/iMEGA/Utils/Categories/UIColor+MNZCategory.h +++ b/iMEGA/Utils/Categories/UIColor+MNZCategory.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM (NSInteger, MEGAChatStatus); diff --git a/iMEGA/Utils/Categories/UIColor+MNZCategory.m b/iMEGA/Utils/Categories/UIColor+MNZCategory.m index de4bdc1881..c76ef7a138 100644 --- a/iMEGA/Utils/Categories/UIColor+MNZCategory.m +++ b/iMEGA/Utils/Categories/UIColor+MNZCategory.m @@ -1,4 +1,3 @@ - #import "UIColor+MNZCategory.h" #import "MEGAChatSdk.h" diff --git a/iMEGA/Utils/Categories/UIDevice+MNZCategory.h b/iMEGA/Utils/Categories/UIDevice+MNZCategory.h index 5d3e0c4b0a..3f9ca59b52 100644 --- a/iMEGA/Utils/Categories/UIDevice+MNZCategory.h +++ b/iMEGA/Utils/Categories/UIDevice+MNZCategory.h @@ -1,4 +1,3 @@ - #import @interface UIDevice (MNZCategory) diff --git a/iMEGA/Utils/Categories/UIDevice+MNZCategory.m b/iMEGA/Utils/Categories/UIDevice+MNZCategory.m index e3e2463bdf..41a9cb5c8c 100644 --- a/iMEGA/Utils/Categories/UIDevice+MNZCategory.m +++ b/iMEGA/Utils/Categories/UIDevice+MNZCategory.m @@ -1,4 +1,3 @@ - #include #import "UIDevice+MNZCategory.h" diff --git a/iMEGA/Utils/Categories/UIFont+MNZCategory.h b/iMEGA/Utils/Categories/UIFont+MNZCategory.h index 8800d3ddd7..35f698184b 100644 --- a/iMEGA/Utils/Categories/UIFont+MNZCategory.h +++ b/iMEGA/Utils/Categories/UIFont+MNZCategory.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Categories/UIFont+MNZCategory.m b/iMEGA/Utils/Categories/UIFont+MNZCategory.m index 064d178267..f88f0dba4b 100644 --- a/iMEGA/Utils/Categories/UIFont+MNZCategory.m +++ b/iMEGA/Utils/Categories/UIFont+MNZCategory.m @@ -1,4 +1,3 @@ - #import "UIFont+MNZCategory.h" @implementation UIFont (MNZCategory) diff --git a/iMEGA/Utils/Categories/UIImage+MNZCategory.h b/iMEGA/Utils/Categories/UIImage+MNZCategory.h index 6a045d09c3..1a18449049 100644 --- a/iMEGA/Utils/Categories/UIImage+MNZCategory.h +++ b/iMEGA/Utils/Categories/UIImage+MNZCategory.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Categories/UIImage+MNZCategory.m b/iMEGA/Utils/Categories/UIImage+MNZCategory.m index e86a944c3f..51cdcab376 100644 --- a/iMEGA/Utils/Categories/UIImage+MNZCategory.m +++ b/iMEGA/Utils/Categories/UIImage+MNZCategory.m @@ -1,4 +1,3 @@ - #import "UIImage+MNZCategory.h" #import "Helper.h" @@ -252,7 +251,7 @@ + (UIImage *)mnz_outgoingFolderImage { } + (UIImage *)mnz_folderCameraUploadsImage { - return [UIImage imageNamed:@"folder_image"]; + return [UIImage imageNamed:@"folder_camera"]; } + (UIImage *)mnz_folderMyChatFilesImage { diff --git a/iMEGA/Utils/Categories/UIImageView+MNZCategory.m b/iMEGA/Utils/Categories/UIImageView+MNZCategory.m index 68733c52ca..0fba401b35 100644 --- a/iMEGA/Utils/Categories/UIImageView+MNZCategory.m +++ b/iMEGA/Utils/Categories/UIImageView+MNZCategory.m @@ -1,4 +1,3 @@ - #import "UIImageView+MNZCategory.h" #import "Helper.h" diff --git a/iMEGA/Utils/Categories/UIKit/ErrorHandling/ErrorHandling.swift b/iMEGA/Utils/Categories/UIKit/ErrorHandling/ErrorHandling.swift index 8e3d47ee3d..e9feec7f3a 100644 --- a/iMEGA/Utils/Categories/UIKit/ErrorHandling/ErrorHandling.swift +++ b/iMEGA/Utils/Categories/UIKit/ErrorHandling/ErrorHandling.swift @@ -9,5 +9,5 @@ protocol ErrorHandling: NSObject { /// - error: The `Error` to be handled. /// - viewController: The view controller instance where the error is originated. /// - retryHandler: An optional handler function that will be triggered with a retry. - func handle(_ error: Error, from viewController: UIViewController, retryHandler: (() -> Void)?) + func handle(_ error: any Error, from viewController: UIViewController, retryHandler: (() -> Void)?) } diff --git a/iMEGA/Utils/Categories/UIKit/ErrorHandling/UIResponder+ErrorHandling.swift b/iMEGA/Utils/Categories/UIKit/ErrorHandling/UIResponder+ErrorHandling.swift index 4e9ba0e2f2..62e750ffc7 100644 --- a/iMEGA/Utils/Categories/UIKit/ErrorHandling/UIResponder+ErrorHandling.swift +++ b/iMEGA/Utils/Categories/UIKit/ErrorHandling/UIResponder+ErrorHandling.swift @@ -3,7 +3,7 @@ import Foundation extension UIResponder: ErrorHandling { @objc func handle( - _ error: Error, + _ error: any Error, from viewController: UIViewController, retryHandler: (() -> Void)? = nil ) { diff --git a/iMEGA/Utils/Categories/UIKit/ErrorHandling/UIViewController+ErrorHandling.swift b/iMEGA/Utils/Categories/UIKit/ErrorHandling/UIViewController+ErrorHandling.swift index 6a36d06151..88501d6892 100644 --- a/iMEGA/Utils/Categories/UIKit/ErrorHandling/UIViewController+ErrorHandling.swift +++ b/iMEGA/Utils/Categories/UIKit/ErrorHandling/UIViewController+ErrorHandling.swift @@ -3,7 +3,7 @@ import Foundation extension UIViewController { func handle( - _ error: Error, + _ error: any Error, retryHandler: (() -> Void)? = nil ) { if let devicePermissionError = error as? DevicePermissionDeniedError { diff --git a/iMEGA/Utils/Categories/UITableView+MNZCategory.h b/iMEGA/Utils/Categories/UITableView+MNZCategory.h index 9e87ba5e10..c828052808 100644 --- a/iMEGA/Utils/Categories/UITableView+MNZCategory.h +++ b/iMEGA/Utils/Categories/UITableView+MNZCategory.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Categories/UITableView+MNZCategory.m b/iMEGA/Utils/Categories/UITableView+MNZCategory.m index 0f60b3eeb4..015346c9bc 100644 --- a/iMEGA/Utils/Categories/UITableView+MNZCategory.m +++ b/iMEGA/Utils/Categories/UITableView+MNZCategory.m @@ -1,4 +1,3 @@ - #import "UITableView+MNZCategory.h" @implementation UITableView (MNZCategory) diff --git a/iMEGA/Utils/Categories/UITextField+MNZCategory.h b/iMEGA/Utils/Categories/UITextField+MNZCategory.h index 0508b1df82..0c26a873c4 100644 --- a/iMEGA/Utils/Categories/UITextField+MNZCategory.h +++ b/iMEGA/Utils/Categories/UITextField+MNZCategory.h @@ -1,4 +1,3 @@ - #import @interface UITextField (MNZCategory) diff --git a/iMEGA/Utils/Categories/UITextField+MNZCategory.m b/iMEGA/Utils/Categories/UITextField+MNZCategory.m index d13b0dac7f..deedd9fb4f 100644 --- a/iMEGA/Utils/Categories/UITextField+MNZCategory.m +++ b/iMEGA/Utils/Categories/UITextField+MNZCategory.m @@ -1,4 +1,3 @@ - #import "UITextField+MNZCategory.h" #import diff --git a/iMEGA/Utils/Categories/UIView+MNZCategory.h b/iMEGA/Utils/Categories/UIView+MNZCategory.h index 76cc3687e5..5b3bed204f 100644 --- a/iMEGA/Utils/Categories/UIView+MNZCategory.h +++ b/iMEGA/Utils/Categories/UIView+MNZCategory.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Categories/UIView+MNZCategory.m b/iMEGA/Utils/Categories/UIView+MNZCategory.m index 2d97baa6e7..029b215d19 100644 --- a/iMEGA/Utils/Categories/UIView+MNZCategory.m +++ b/iMEGA/Utils/Categories/UIView+MNZCategory.m @@ -1,4 +1,3 @@ - #import "UIView+MNZCategory.h" @implementation UIView (MNZCategory) diff --git a/iMEGA/Utils/Categories/UIViewController+Additions.swift b/iMEGA/Utils/Categories/UIViewController+Additions.swift index 4f4beb1437..2edccdbe50 100644 --- a/iMEGA/Utils/Categories/UIViewController+Additions.swift +++ b/iMEGA/Utils/Categories/UIViewController+Additions.swift @@ -1,4 +1,3 @@ - import Foundation extension UIViewController { diff --git a/iMEGA/Utils/Categories/UIViewController+MNZCategory.h b/iMEGA/Utils/Categories/UIViewController+MNZCategory.h index 95129cf8d4..889dce697a 100644 --- a/iMEGA/Utils/Categories/UIViewController+MNZCategory.h +++ b/iMEGA/Utils/Categories/UIViewController+MNZCategory.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Categories/UIViewController+MNZCategory.m b/iMEGA/Utils/Categories/UIViewController+MNZCategory.m index 496430314a..697eb358a4 100644 --- a/iMEGA/Utils/Categories/UIViewController+MNZCategory.m +++ b/iMEGA/Utils/Categories/UIViewController+MNZCategory.m @@ -1,4 +1,3 @@ - #import "UIViewController+MNZCategory.h" #import diff --git a/iMEGA/Utils/ContextMenus/ContextMenuDataModel.swift b/iMEGA/Utils/ContextMenus/ContextMenuDataModel.swift index 45245eeb16..27728c0477 100644 --- a/iMEGA/Utils/ContextMenus/ContextMenuDataModel.swift +++ b/iMEGA/Utils/ContextMenus/ContextMenuDataModel.swift @@ -1,4 +1,3 @@ - struct ContextMenuDataModel { var identifier: String? var title: String? diff --git a/iMEGA/Utils/ContextMenus/ContextMenuModel+Mapper.swift b/iMEGA/Utils/ContextMenus/ContextMenuModel+Mapper.swift index 57324e5295..1c70c4f2ee 100644 --- a/iMEGA/Utils/ContextMenus/ContextMenuModel+Mapper.swift +++ b/iMEGA/Utils/ContextMenus/ContextMenuModel+Mapper.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n extension CMActionEntity { func toContextMenuModel() -> ContextMenuModel { diff --git a/iMEGA/Utils/CoreData/MEGACoreDataStack.h b/iMEGA/Utils/CoreData/MEGACoreDataStack.h index b0485aec39..36f4e1d8b0 100644 --- a/iMEGA/Utils/CoreData/MEGACoreDataStack.h +++ b/iMEGA/Utils/CoreData/MEGACoreDataStack.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/CoreData/MEGAStore+Message.swift b/iMEGA/Utils/CoreData/MEGAStore+Message.swift index dcd7595a33..e7a43b86a4 100644 --- a/iMEGA/Utils/CoreData/MEGAStore+Message.swift +++ b/iMEGA/Utils/CoreData/MEGAStore+Message.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/MEGAStore.m b/iMEGA/Utils/CoreData/MEGAStore.m index 23ce0b0036..6ebde12e3e 100644 --- a/iMEGA/Utils/CoreData/MEGAStore.m +++ b/iMEGA/Utils/CoreData/MEGAStore.m @@ -1,4 +1,3 @@ - #import "MEGAStore.h" #import "NSString+MNZCategory.h" #import "CoreDataErrorHandler.h" diff --git a/iMEGA/Utils/CoreData/MOUploadTransfer+Mapper.swift b/iMEGA/Utils/CoreData/MOUploadTransfer+Mapper.swift index 8ea72c0862..fd89568d76 100644 --- a/iMEGA/Utils/CoreData/MOUploadTransfer+Mapper.swift +++ b/iMEGA/Utils/CoreData/MOUploadTransfer+Mapper.swift @@ -1,4 +1,3 @@ - extension MOUploadTransfer { @objc func toUploadTransferEntity() -> TransferRecordDTO? { if let localIdentifier = localIdentifier, let parentNodeHandle = parentNodeHandle { diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/AppearancePreference+CoreDataClass.swift b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/AppearancePreference+CoreDataClass.swift index 6ad0767599..22590873ee 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/AppearancePreference+CoreDataClass.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/AppearancePreference+CoreDataClass.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/AppearancePreference+CoreDataProperties.swift b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/AppearancePreference+CoreDataProperties.swift index 6c5e10515f..0503720abe 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/AppearancePreference+CoreDataProperties.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/AppearancePreference+CoreDataProperties.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/CloudAppearancePreference+CoreDataClass.swift b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/CloudAppearancePreference+CoreDataClass.swift index 3d1cd7386e..50f1185333 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/CloudAppearancePreference+CoreDataClass.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/CloudAppearancePreference+CoreDataClass.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/CloudAppearancePreference+CoreDataProperties.swift b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/CloudAppearancePreference+CoreDataProperties.swift index 4256110390..f2f1445789 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/CloudAppearancePreference+CoreDataProperties.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/CloudAppearancePreference+CoreDataProperties.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MEGAStore+CloudAppearancePreference.swift b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MEGAStore+CloudAppearancePreference.swift index 1f37db307d..6e42f3ea37 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MEGAStore+CloudAppearancePreference.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MEGAStore+CloudAppearancePreference.swift @@ -1,4 +1,3 @@ - import Foundation extension MEGAStore { diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MEGAStore+OfflineAppearancePreference.swift b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MEGAStore+OfflineAppearancePreference.swift index 9a78b52fd0..47b0512521 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MEGAStore+OfflineAppearancePreference.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MEGAStore+OfflineAppearancePreference.swift @@ -1,4 +1,3 @@ - import Foundation extension MEGAStore { diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataClass.h b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataClass.h index addd4a1d49..f5dabd0fe4 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataClass.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataClass.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataClass.m b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataClass.m index 5af06c898b..dc8bd3479a 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataClass.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataClass.m @@ -1,4 +1,3 @@ - #import "MOFolderLayout+CoreDataClass.h" @implementation MOFolderLayout diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataProperties.h b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataProperties.h index 4ad982553e..5e8644a636 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataProperties.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataProperties.h @@ -1,4 +1,3 @@ - #import "MOFolderLayout+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataProperties.m b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataProperties.m index 1df55e1ed0..8d41215de3 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataProperties.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOFolderLayout+CoreDataProperties.m @@ -1,4 +1,3 @@ - #import "MOFolderLayout+CoreDataProperties.h" @implementation MOFolderLayout (CoreDataProperties) diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataClass.h b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataClass.h index 4701d79c2d..b7dbf5b37e 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataClass.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataClass.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataClass.m b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataClass.m index 96c6b3a2d2..945ccfe3a6 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataClass.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataClass.m @@ -1,4 +1,3 @@ - #import "MOOfflineFolderLayout+CoreDataClass.h" @implementation MOOfflineFolderLayout diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataProperties.h b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataProperties.h index 18d65b303c..e1a4403e22 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataProperties.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataProperties.h @@ -1,4 +1,3 @@ - #import "MOOfflineFolderLayout+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataProperties.m b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataProperties.m index c6c1327a0b..df55d34852 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataProperties.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/MOOfflineFolderLayout+CoreDataProperties.m @@ -1,4 +1,3 @@ - #import "MOOfflineFolderLayout+CoreDataProperties.h" @implementation MOOfflineFolderLayout (CoreDataProperties) diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/OfflineAppearancePreference+CoreDataClass.swift b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/OfflineAppearancePreference+CoreDataClass.swift index 3daa42a51f..a203f36dd1 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/OfflineAppearancePreference+CoreDataClass.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/OfflineAppearancePreference+CoreDataClass.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/OfflineAppearancePreference+CoreDataProperties.swift b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/OfflineAppearancePreference+CoreDataProperties.swift index 49d65fecde..b27aace599 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Appearance/OfflineAppearancePreference+CoreDataProperties.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Appearance/OfflineAppearancePreference+CoreDataProperties.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataClass.h b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataClass.h index 28d0ad9b72..4eaa623bf7 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataClass.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataClass.h @@ -1,4 +1,3 @@ - #import #import "MOAssetUploadErrorRecord+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataClass.m b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataClass.m index 105e83d994..3ef71c0d24 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataClass.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataClass.m @@ -1,4 +1,3 @@ - #import "MOAssetUploadErrorPerLaunch+CoreDataClass.h" @implementation MOAssetUploadErrorPerLaunch diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataProperties.h b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataProperties.h index 4b1dc1dbc3..0cbfa61c33 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataProperties.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataProperties.h @@ -1,4 +1,3 @@ - #import "MOAssetUploadErrorPerLaunch+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataProperties.m b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataProperties.m index 81c781bae9..539ce34c6c 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataProperties.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLaunch+CoreDataProperties.m @@ -1,4 +1,3 @@ - #import "MOAssetUploadErrorPerLaunch+CoreDataProperties.h" @implementation MOAssetUploadErrorPerLaunch (CoreDataProperties) diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataClass.h b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataClass.h index bd5d203245..cb2de5d80d 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataClass.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataClass.h @@ -1,4 +1,3 @@ - #import #import "MOAssetUploadErrorRecord+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataClass.m b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataClass.m index 89aa490c6a..00f3b9055c 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataClass.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataClass.m @@ -1,4 +1,3 @@ - #import "MOAssetUploadErrorPerLogin+CoreDataClass.h" @implementation MOAssetUploadErrorPerLogin diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataProperties.h b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataProperties.h index bcd51b5b5b..6f46a877b5 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataProperties.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataProperties.h @@ -1,4 +1,3 @@ - #import "MOAssetUploadErrorPerLogin+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataProperties.m b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataProperties.m index 1bb99ba687..9cb713cd47 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataProperties.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorPerLogin+CoreDataProperties.m @@ -1,4 +1,3 @@ - #import "MOAssetUploadErrorPerLogin+CoreDataProperties.h" @implementation MOAssetUploadErrorPerLogin (CoreDataProperties) diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataClass.h b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataClass.h index 8e2b7228b4..6a1ffc7126 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataClass.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataClass.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataClass.m b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataClass.m index 62b22964cd..c787f9a863 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataClass.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataClass.m @@ -1,4 +1,3 @@ - #import "MOAssetUploadErrorRecord+CoreDataClass.h" @implementation MOAssetUploadErrorRecord diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataProperties.h b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataProperties.h index 14d6ef21b6..194badb31c 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataProperties.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataProperties.h @@ -1,4 +1,3 @@ - #import "MOAssetUploadErrorRecord+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataProperties.m b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataProperties.m index f0405e783c..cf2c7bf907 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataProperties.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadErrorRecord+CoreDataProperties.m @@ -1,4 +1,3 @@ - #import "MOAssetUploadErrorRecord+CoreDataProperties.h" @implementation MOAssetUploadErrorRecord (CoreDataProperties) diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataClass.h b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataClass.h index 72ec98b6bb..d8c80343fa 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataClass.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataClass.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataClass.m b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataClass.m index bfe6dcd912..47dc84bca9 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataClass.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataClass.m @@ -1,4 +1,3 @@ - #import "MOAssetUploadFileNameRecord+CoreDataClass.h" @implementation MOAssetUploadFileNameRecord diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataProperties.h b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataProperties.h index 1da713819e..cc37814a8c 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataProperties.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataProperties.h @@ -1,4 +1,3 @@ - #import "MOAssetUploadFileNameRecord+CoreDataClass.h" NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataProperties.m b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataProperties.m index 585885989f..f4433e91cc 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataProperties.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadFileNameRecord+CoreDataProperties.m @@ -1,4 +1,3 @@ - #import "MOAssetUploadFileNameRecord+CoreDataProperties.h" @implementation MOAssetUploadFileNameRecord (CoreDataProperties) diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataClass.h b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataClass.h index 6d5464f190..a0586cbc5d 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataClass.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataClass.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataClass.m b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataClass.m index ae68a9dfb4..e35d2a1fb1 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataClass.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataClass.m @@ -1,4 +1,3 @@ - #import "MOAssetUploadRecord+CoreDataClass.h" @implementation MOAssetUploadRecord diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataProperties.h b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataProperties.h index a731838ab2..615e24f2bf 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataProperties.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataProperties.h @@ -1,4 +1,3 @@ - #import "MOAssetUploadRecord+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataProperties.m b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataProperties.m index 03585aac8c..d4ce02e85c 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataProperties.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/CameraUploads/MOAssetUploadRecord+CoreDataProperties.m @@ -1,4 +1,3 @@ - #import "MOAssetUploadRecord+CoreDataProperties.h" @implementation MOAssetUploadRecord (CoreDataProperties) diff --git a/iMEGA/Utils/CoreData/ManagedObjects/ChatUploadTransfer+CoreDataClass.swift b/iMEGA/Utils/CoreData/ManagedObjects/ChatUploadTransfer+CoreDataClass.swift index 508efff17d..88c5c64a9c 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/ChatUploadTransfer+CoreDataClass.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/ChatUploadTransfer+CoreDataClass.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/ManagedObjects/ChatUploadTransfer+CoreDataProperties.swift b/iMEGA/Utils/CoreData/ManagedObjects/ChatUploadTransfer+CoreDataProperties.swift index 7af5ea16fc..9068e4f41f 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/ChatUploadTransfer+CoreDataProperties.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/ChatUploadTransfer+CoreDataProperties.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataClass.h b/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataClass.h index af06fbab4d..41177a8264 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataClass.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataClass.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataClass.m b/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataClass.m index edecf119ab..c620817674 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataClass.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataClass.m @@ -1,4 +1,3 @@ - #import "MOChatDraft+CoreDataClass.h" @implementation MOChatDraft diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataProperties.h b/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataProperties.h index e327f9e4d5..62b37aa0e4 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataProperties.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataProperties.h @@ -1,4 +1,3 @@ - #import "MOChatDraft+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataProperties.m b/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataProperties.m index 400c151852..a8c749c3bf 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataProperties.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOChatDraft+CoreDataProperties.m @@ -1,4 +1,3 @@ - #import "MOChatDraft+CoreDataProperties.h" @implementation MOChatDraft (CoreDataProperties) diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataClass.h b/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataClass.h index f5ab614d2a..9b4d5c9c78 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataClass.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataClass.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataClass.m b/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataClass.m index 78396ac9b2..6a859710b8 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataClass.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataClass.m @@ -1,4 +1,3 @@ - #import "MOMediaDestination+CoreDataClass.h" @implementation MOMediaDestination diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataProperties.h b/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataProperties.h index 63c0bf3f6c..82a7738088 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataProperties.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataProperties.h @@ -1,4 +1,3 @@ - #import "MOMediaDestination+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataProperties.m b/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataProperties.m index 84e295eb9c..d8a40e85ea 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataProperties.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOMediaDestination+CoreDataProperties.m @@ -1,4 +1,3 @@ - #import "MOMediaDestination+CoreDataProperties.h" @implementation MOMediaDestination (CoreDataProperties) diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataClass.h b/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataClass.h index dbfc693f28..5adf9e0fe9 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataClass.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataClass.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataClass.m b/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataClass.m index eb0ce7d8f9..4c9bfa0f23 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataClass.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataClass.m @@ -1,4 +1,3 @@ - #import "MOMessage+CoreDataClass.h" @implementation MOMessage diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataProperties.h b/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataProperties.h index 03e91af86f..5dc375a957 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataProperties.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataProperties.h @@ -1,4 +1,3 @@ - #import "MOMessage+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataProperties.m b/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataProperties.m index 92090559a1..c6f3a3313b 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataProperties.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOMessage+CoreDataProperties.m @@ -1,4 +1,3 @@ - #import "MOMessage+CoreDataProperties.h" @implementation MOMessage (CoreDataProperties) diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataClass.h b/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataClass.h index 2967561fd7..5639c2e75e 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataClass.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataClass.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataClass.m b/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataClass.m index 8b732e73af..228585169f 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataClass.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataClass.m @@ -1,4 +1,3 @@ - #import "MOUploadTransfer+CoreDataClass.h" @implementation MOUploadTransfer diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataProperties.h b/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataProperties.h index cb5b2ebc5b..a754762ab8 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataProperties.h +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataProperties.h @@ -1,4 +1,3 @@ - #import "MOUploadTransfer+CoreDataClass.h" diff --git a/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataProperties.m b/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataProperties.m index 61454edb9f..ebccc0fac1 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataProperties.m +++ b/iMEGA/Utils/CoreData/ManagedObjects/MOUploadTransfer+CoreDataProperties.m @@ -1,4 +1,3 @@ - #import "MOUploadTransfer+CoreDataProperties.h" @implementation MOUploadTransfer (CoreDataProperties) diff --git a/iMEGA/Utils/CoreData/ManagedObjects/User/MEGAStore+MOUser.swift b/iMEGA/Utils/CoreData/ManagedObjects/User/MEGAStore+MOUser.swift index 9f6c2fddd6..dc0c788ebd 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/User/MEGAStore+MOUser.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/User/MEGAStore+MOUser.swift @@ -1,4 +1,3 @@ - import Foundation extension MEGAStore { diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetFavouriteItem+CoreDataClass.swift b/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetFavouriteItem+CoreDataClass.swift index e2444477e1..0ef74f2248 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetFavouriteItem+CoreDataClass.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetFavouriteItem+CoreDataClass.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetFavouriteItem+CoreDataProperties.swift b/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetFavouriteItem+CoreDataProperties.swift index d8f9fd1a70..6906e71590 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetFavouriteItem+CoreDataProperties.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetFavouriteItem+CoreDataProperties.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetRecentItem+CoreDataClass.swift b/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetRecentItem+CoreDataClass.swift index ccfd7182e5..1f2fb37479 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetRecentItem+CoreDataClass.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetRecentItem+CoreDataClass.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetRecentItem+CoreDataProperties.swift b/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetRecentItem+CoreDataProperties.swift index 39bf6ac5ec..5912e76e95 100644 --- a/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetRecentItem+CoreDataProperties.swift +++ b/iMEGA/Utils/CoreData/ManagedObjects/Widget/QuickAccess/QuickAccessWidgetRecentItem+CoreDataProperties.swift @@ -1,4 +1,3 @@ - import CoreData import Foundation diff --git a/iMEGA/Utils/Delegates/FileLinkActionViewControllerDelegate.swift b/iMEGA/Utils/Delegates/FileLinkActionViewControllerDelegate.swift index 5795ca8a6a..b87a9298a8 100644 --- a/iMEGA/Utils/Delegates/FileLinkActionViewControllerDelegate.swift +++ b/iMEGA/Utils/Delegates/FileLinkActionViewControllerDelegate.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n import MEGASDKRepo final class FileLinkActionViewControllerDelegate: NSObject, NodeActionViewControllerDelegate { @@ -42,7 +43,7 @@ final class FileLinkActionViewControllerDelegate: NSObject, NodeActionViewContro private func saveToPhotos(node: MEGANode) { TransfersWidgetViewController.sharedTransfer().bringProgressToFrontKeyWindowIfNeeded() - let saveMediaToPhotosUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: MEGASdkManager.sharedMEGASdk()), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) + let saveMediaToPhotosUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: .shared), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) Task { @MainActor in do { @@ -79,20 +80,20 @@ extension FileLinkActionViewControllerDelegate: SendToViewControllerDelegate { viewController.dismiss(animated: true, completion: nil) chats.forEach { - MEGASdkManager.sharedMEGAChatSdk().sendMessage(toChat: $0.chatId, message: link) + MEGAChatSdk.shared.sendMessage(toChat: $0.chatId, message: link) } users.forEach { - let chatRoom = MEGASdkManager.sharedMEGAChatSdk().chatRoom(byUser: $0.handle) + let chatRoom = MEGAChatSdk.shared.chatRoom(byUser: $0.handle) if chatRoom != nil { guard let chatId = chatRoom?.chatId else { return } - MEGASdkManager.sharedMEGAChatSdk().sendMessage(toChat: chatId, message: link) + MEGAChatSdk.shared.sendMessage(toChat: chatId, message: link) } else { MEGALogDebug("There is not a chat with %@, create the chat and send message", $0.email) - MEGASdkManager.sharedMEGAChatSdk().mnz_createChatRoom(userHandle: $0.handle, completion: { - MEGASdkManager.sharedMEGAChatSdk().sendMessage(toChat: $0.chatId, message: self.link) + MEGAChatSdk.shared.mnz_createChatRoom(userHandle: $0.handle, completion: { + MEGAChatSdk.shared.sendMessage(toChat: $0.chatId, message: self.link) }) } } diff --git a/iMEGA/Utils/Delegates/NodeActionViewControllerGenericDelegate.swift b/iMEGA/Utils/Delegates/NodeActionViewControllerGenericDelegate.swift index a2a15adad9..67a879d0e2 100644 --- a/iMEGA/Utils/Delegates/NodeActionViewControllerGenericDelegate.swift +++ b/iMEGA/Utils/Delegates/NodeActionViewControllerGenericDelegate.swift @@ -2,14 +2,14 @@ import Foundation import MEGADomain import MEGASDKRepo -final class NodeActionViewControllerGenericDelegate: +class NodeActionViewControllerGenericDelegate: NodeActionViewControllerDelegate { private weak var viewController: UIViewController? private(set) var isNodeFromFolderLink: Bool private(set) var messageId: HandleEntity? private(set) var chatId: HandleEntity? - private let saveMediaToPhotosUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: MEGASdkManager.sharedMEGASdk()), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) + private let saveMediaToPhotosUseCase = SaveMediaToPhotosUseCase(downloadFileRepository: DownloadFileRepository(sdk: MEGASdk.shared), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo) init(viewController: UIViewController, isNodeFromFolderLink: Bool = false, messageId: HandleEntity? = nil, chatId: HandleEntity? = nil) { self.viewController = viewController @@ -233,16 +233,14 @@ final class NodeActionViewControllerGenericDelegate: } private func favourite(_ node: MEGANode) { - let nodefavouriteActionUseCase = NodeFavouriteActionUseCase(nodeFavouriteRepository: NodeFavouriteActionRepository(sdk: MEGASdkManager.sharedMEGASdk())) + let nodefavouriteActionUseCase = NodeFavouriteActionUseCase(nodeFavouriteRepository: NodeFavouriteActionRepository.newRepo) if node.isFavourite { Task { try await nodefavouriteActionUseCase.unFavourite(node: node.toNodeEntity()) - QuickAccessWidgetManager().deleteFavouriteItem(for: node) } } else { Task { try await nodefavouriteActionUseCase.favourite(node: node.toNodeEntity()) - QuickAccessWidgetManager().insertFavouriteItem(for: node) } } } diff --git a/iMEGA/Utils/DomainExtensions/AlbumNameUseCase+Additions.swift b/iMEGA/Utils/DomainExtensions/AlbumNameUseCase+Additions.swift index 72f3dce4a3..44d4b2886a 100644 --- a/iMEGA/Utils/DomainExtensions/AlbumNameUseCase+Additions.swift +++ b/iMEGA/Utils/DomainExtensions/AlbumNameUseCase+Additions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n extension AlbumNameUseCaseProtocol { func reservedAlbumNames() async -> [String] { diff --git a/iMEGA/Utils/DomainExtensions/SaveMediaToPhotosErrorEntity+Additions.swift b/iMEGA/Utils/DomainExtensions/SaveMediaToPhotosErrorEntity+Additions.swift index 232da4b253..869760adbc 100644 --- a/iMEGA/Utils/DomainExtensions/SaveMediaToPhotosErrorEntity+Additions.swift +++ b/iMEGA/Utils/DomainExtensions/SaveMediaToPhotosErrorEntity+Additions.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n extension SaveMediaToPhotosErrorEntity: LocalizedError { public var errorDescription: String? { diff --git a/iMEGA/Utils/Headers/CallPeerAudio.h b/iMEGA/Utils/Headers/CallPeerAudio.h index ed310f4b52..be2089cc2e 100644 --- a/iMEGA/Utils/Headers/CallPeerAudio.h +++ b/iMEGA/Utils/Headers/CallPeerAudio.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSUInteger, CallPeerAudio) { CallPeerAudioOff, CallPeerAudioOn, diff --git a/iMEGA/Utils/Headers/CallPeerVideo.h b/iMEGA/Utils/Headers/CallPeerVideo.h index c9dbf60d8b..fdc1ec513c 100644 --- a/iMEGA/Utils/Headers/CallPeerVideo.h +++ b/iMEGA/Utils/Headers/CallPeerVideo.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSUInteger, CallPeerVideo) { CallPeerVideoOff, CallPeerVideoOn, diff --git a/iMEGA/Utils/Headers/CallType.h b/iMEGA/Utils/Headers/CallType.h index 8a681c6088..891e062737 100644 --- a/iMEGA/Utils/Headers/CallType.h +++ b/iMEGA/Utils/Headers/CallType.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSUInteger, CallType) { CallTypeIncoming, CallTypeOutgoing, diff --git a/iMEGA/Utils/Headers/ChatImageUploadQuality.h b/iMEGA/Utils/Headers/ChatImageUploadQuality.h index 94ad282735..cc081b1336 100644 --- a/iMEGA/Utils/Headers/ChatImageUploadQuality.h +++ b/iMEGA/Utils/Headers/ChatImageUploadQuality.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSUInteger, ChatImageUploadQuality) { ChatImageUploadQualityAuto, ChatImageUploadQualityOriginal, diff --git a/iMEGA/Utils/Headers/ChatRoomsType.h b/iMEGA/Utils/Headers/ChatRoomsType.h index c51a057065..8ad755b4ce 100644 --- a/iMEGA/Utils/Headers/ChatRoomsType.h +++ b/iMEGA/Utils/Headers/ChatRoomsType.h @@ -1,4 +1,3 @@ - typedef NS_ENUM (NSInteger, ChatRoomsType) { ChatRoomsTypeDefault = 0, ChatRoomsTypeArchived diff --git a/iMEGA/Utils/Headers/ChatStatus.swift b/iMEGA/Utils/Headers/ChatStatus.swift index de129ad234..ee52a39bd4 100644 --- a/iMEGA/Utils/Headers/ChatStatus.swift +++ b/iMEGA/Utils/Headers/ChatStatus.swift @@ -1,3 +1,4 @@ +import MEGAL10n enum ChatStatus: Int, CaseIterable { case offline = 1 diff --git a/iMEGA/Utils/Headers/ChatVideoUploadQuality.h b/iMEGA/Utils/Headers/ChatVideoUploadQuality.h index 228b048fd1..5b5d3bd49e 100644 --- a/iMEGA/Utils/Headers/ChatVideoUploadQuality.h +++ b/iMEGA/Utils/Headers/ChatVideoUploadQuality.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSUInteger, ChatVideoUploadQuality) { ChatVideoUploadQualityVeryLow = 0, // Deprecated ChatVideoUploadQualityLow = 1, diff --git a/iMEGA/Utils/Headers/ContacLinkQRType.h b/iMEGA/Utils/Headers/ContacLinkQRType.h index 87f9874281..bbab4448da 100644 --- a/iMEGA/Utils/Headers/ContacLinkQRType.h +++ b/iMEGA/Utils/Headers/ContacLinkQRType.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSUInteger, ContactLinkQRType) { ContactLinkQRTypeContactRequest, ContactLinkQRTypeShareFolder, diff --git a/iMEGA/Utils/Headers/DisplayMode.h b/iMEGA/Utils/Headers/DisplayMode.h index 26a118a19e..171dd7e50a 100644 --- a/iMEGA/Utils/Headers/DisplayMode.h +++ b/iMEGA/Utils/Headers/DisplayMode.h @@ -1,4 +1,3 @@ - typedef NS_ENUM (NSInteger, DisplayMode) { DisplayModeUnknown = -1, DisplayModeCloudDrive = 0, diff --git a/iMEGA/Utils/Headers/JoinViewState.h b/iMEGA/Utils/Headers/JoinViewState.h index cbd349da00..4197abacb5 100644 --- a/iMEGA/Utils/Headers/JoinViewState.h +++ b/iMEGA/Utils/Headers/JoinViewState.h @@ -1,4 +1,3 @@ - #ifndef JoinViewState_h #define JoinViewState_h diff --git a/iMEGA/Utils/Headers/LinkOption.h b/iMEGA/Utils/Headers/LinkOption.h index e9ee6f84c2..72298421c8 100644 --- a/iMEGA/Utils/Headers/LinkOption.h +++ b/iMEGA/Utils/Headers/LinkOption.h @@ -1,4 +1,3 @@ - typedef NS_ENUM (NSUInteger, LinkOption) { LinkOptionDefault, LinkOptionImportNode, diff --git a/iMEGA/Utils/Headers/MegaNodeActionType.h b/iMEGA/Utils/Headers/MegaNodeActionType.h index 3fd2723707..6df072ece2 100644 --- a/iMEGA/Utils/Headers/MegaNodeActionType.h +++ b/iMEGA/Utils/Headers/MegaNodeActionType.h @@ -1,4 +1,3 @@ - typedef NS_ENUM (NSInteger, MegaNodeActionType) { MegaNodeActionTypeDownload = 0, MegaNodeActionTypeExportFile, diff --git a/iMEGA/Utils/Headers/NodeSelectionType.swift b/iMEGA/Utils/Headers/NodeSelectionType.swift index 5d49dbfab6..726a5a3077 100644 --- a/iMEGA/Utils/Headers/NodeSelectionType.swift +++ b/iMEGA/Utils/Headers/NodeSelectionType.swift @@ -1,4 +1,3 @@ - enum NodeSelectionType: String { case single case files diff --git a/iMEGA/Utils/Headers/OnboardingType.h b/iMEGA/Utils/Headers/OnboardingType.h index 5d114d4986..fe91158067 100644 --- a/iMEGA/Utils/Headers/OnboardingType.h +++ b/iMEGA/Utils/Headers/OnboardingType.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSUInteger, OnboardingType) { OnboardingTypeDefault, OnboardingTypePermissions diff --git a/iMEGA/Utils/Headers/OnboardingViewType.h b/iMEGA/Utils/Headers/OnboardingViewType.h index 33f78badd4..d04a525070 100644 --- a/iMEGA/Utils/Headers/OnboardingViewType.h +++ b/iMEGA/Utils/Headers/OnboardingViewType.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSUInteger, OnboardingViewType) { OnboardingViewTypeEncryptionInfo, OnboardingViewTypeChatInfo, diff --git a/iMEGA/Utils/Headers/SendMode.h b/iMEGA/Utils/Headers/SendMode.h index 3f93791206..7959f9a471 100644 --- a/iMEGA/Utils/Headers/SendMode.h +++ b/iMEGA/Utils/Headers/SendMode.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSUInteger, SendMode) { SendModeCloud, SendModeShareExtension, diff --git a/iMEGA/Utils/Headers/ShareAttachmentType.h b/iMEGA/Utils/Headers/ShareAttachmentType.h index 3d9a79e694..0ad1aea034 100644 --- a/iMEGA/Utils/Headers/ShareAttachmentType.h +++ b/iMEGA/Utils/Headers/ShareAttachmentType.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSUInteger, ShareAttachmentType) { ShareAttachmentTypePNG, ShareAttachmentTypeGIF, diff --git a/iMEGA/Utils/Headers/SortingPreference.swift b/iMEGA/Utils/Headers/SortingPreference.swift index 7b85be66e1..bab9b65f2f 100644 --- a/iMEGA/Utils/Headers/SortingPreference.swift +++ b/iMEGA/Utils/Headers/SortingPreference.swift @@ -1,4 +1,3 @@ - @objc enum SortingPreference: Int { case perFolder = 0 case sameForAll = 1 diff --git a/iMEGA/Utils/Headers/ToolbarType.h b/iMEGA/Utils/Headers/ToolbarType.h index bef0609745..ba47c54950 100644 --- a/iMEGA/Utils/Headers/ToolbarType.h +++ b/iMEGA/Utils/Headers/ToolbarType.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSUInteger, ToolbarType) { ToolbarTypeForward, ToolbarTypeDelete diff --git a/iMEGA/Utils/Headers/TransfersSelected.h b/iMEGA/Utils/Headers/TransfersSelected.h index c1b58652e2..1de1767e0f 100644 --- a/iMEGA/Utils/Headers/TransfersSelected.h +++ b/iMEGA/Utils/Headers/TransfersSelected.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSInteger, TransfersSelected) { TransfersSelectedAll = 0, TransfersSelectedDownloads = 1, diff --git a/iMEGA/Utils/Headers/TransfersWidgetSelected.h b/iMEGA/Utils/Headers/TransfersWidgetSelected.h index 18a1b3b5cc..2d36dda262 100644 --- a/iMEGA/Utils/Headers/TransfersWidgetSelected.h +++ b/iMEGA/Utils/Headers/TransfersWidgetSelected.h @@ -1,4 +1,3 @@ - typedef NS_ENUM(NSInteger, TransfersWidgetSelected) { TransfersWidgetSelectedAll = 0, TransfersWidgetSelectedCompleted = 1, diff --git a/iMEGA/Utils/Headers/TwoFactorAuthentication.h b/iMEGA/Utils/Headers/TwoFactorAuthentication.h index a979ad5e09..08df027705 100644 --- a/iMEGA/Utils/Headers/TwoFactorAuthentication.h +++ b/iMEGA/Utils/Headers/TwoFactorAuthentication.h @@ -1,4 +1,3 @@ - typedef NS_ENUM (NSInteger, TwoFactorAuthentication) { TwoFactorAuthenticationLogin = 0, TwoFactorAuthenticationChangePassword, diff --git a/iMEGA/Utils/Headers/URLType.h b/iMEGA/Utils/Headers/URLType.h index 54a7e3da03..b2c9794e51 100644 --- a/iMEGA/Utils/Headers/URLType.h +++ b/iMEGA/Utils/Headers/URLType.h @@ -1,4 +1,3 @@ - typedef NS_ENUM (NSUInteger, URLType) { URLTypeDefault, URLTypeFileLink, diff --git a/iMEGA/Utils/Headers/ViewModePreference.swift b/iMEGA/Utils/Headers/ViewModePreference.swift index e2426bc976..6b18bfe416 100644 --- a/iMEGA/Utils/Headers/ViewModePreference.swift +++ b/iMEGA/Utils/Headers/ViewModePreference.swift @@ -1,4 +1,3 @@ - @objc enum ViewModePreference: Int { case perFolder = 0 case list = 1 diff --git a/iMEGA/Utils/Helper.m b/iMEGA/Utils/Helper.m index 375ab1cf09..afbe710dbb 100644 --- a/iMEGA/Utils/Helper.m +++ b/iMEGA/Utils/Helper.m @@ -13,7 +13,6 @@ #import "MEGACopyRequestDelegate.h" #import "MEGACreateFolderRequestDelegate.h" -#import "MEGAGenericRequestDelegate.h" #import "MEGANode+MNZCategory.h" #import "MEGANodeList+MNZCategory.h" #import "MEGAProcessAsset.h" @@ -32,6 +31,8 @@ #import "NodeTableViewCell.h" #import "PhotoCollectionViewCell.h" +@import MEGAL10nObjc; + @implementation Helper #pragma mark - Paths @@ -197,7 +198,7 @@ + (void)startUploadTransferWithTransferRecordDTO:(TransferRecordDTO *)transferRe } [[MEGAStore shareInstance] deleteUploadTransferWithLocalIdentifier:transferRecordDTO.localIdentifier]; } error:^(NSError *error) { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudError"] status:[NSString stringWithFormat:@"%@ %@ \r %@", NSLocalizedString(@"Transfer failed:", nil), asset.localIdentifier, error.localizedDescription]]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudError"] status:[NSString stringWithFormat:@"%@ %@ \r %@", LocalizedString(@"Transfer failed:", @""), asset.localIdentifier, error.localizedDescription]]; [[MEGAStore shareInstance] deleteUploadTransferWithLocalIdentifier:transferRecordDTO.localIdentifier]; [Helper startPendingUploadTransferIfNeeded]; }]; @@ -298,28 +299,28 @@ + (MEGASortOrderType)defaultSortType { } + (void)changeApiURL { - NSString *alertTitle = NSLocalizedString(@"Change to a test server?", @"title of the alert dialog when the user is changing the API URL to staging"); - NSString *alertMessage = NSLocalizedString(@"Are you sure you want to change to a test server? Your account may suffer irrecoverable problems", @"text of the alert dialog when the user is changing the API URL to staging"); + NSString *alertTitle = LocalizedString(@"Change to a test server?", @"title of the alert dialog when the user is changing the API URL to staging"); + NSString *alertMessage = LocalizedString(@"Are you sure you want to change to a test server? Your account may suffer irrecoverable problems", @"text of the alert dialog when the user is changing the API URL to staging"); UIAlertController *changeApiServerAlertController = [UIAlertController alertControllerWithTitle:alertTitle message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - [changeApiServerAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [changeApiServerAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - [changeApiServerAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Production", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [changeApiServerAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"Production", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [Helper setApiURL:MEGAAPIEnvProduction]; [Helper apiURLChanged]; }]]; - [changeApiServerAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"staging", @"Button title to cancel something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [changeApiServerAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"staging", @"Button title to cancel something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [Helper setApiURL:MEGAAPIEnvStaging]; [Helper apiURLChanged]; }]]; - [changeApiServerAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Staging:444", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [changeApiServerAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"Staging:444", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [Helper setApiURL:MEGAAPIEnvStaging444]; [Helper apiURLChanged]; }]]; - [changeApiServerAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Sandbox3", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [changeApiServerAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"Sandbox3", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [Helper setApiURL:MEGAAPIEnvSandbox3]; [Helper apiURLChanged]; }]]; @@ -367,8 +368,8 @@ + (void)restoreAPISetting { } + (void)cannotPlayContentDuringACallAlert { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:NSLocalizedString(@"It is not possible to play content while there is a call in progress", @"Message shown when there is an ongoing call and the user tries to play an audio or video") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:LocalizedString(@"It is not possible to play content while there is a call in progress", @"Message shown when there is an ongoing call and the user tries to play an audio or video") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; } @@ -377,9 +378,9 @@ + (UIAlertController *)removeUserContactFromSender:(UIView *)sender withConfirmA UIAlertController *removeContactAlertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; - [removeContactAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:nil]]; + [removeContactAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:nil]]; - [removeContactAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"removeUserTitle", @"Alert title shown when you want to remove one or more contacts") style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + [removeContactAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"removeUserTitle", @"Alert title shown when you want to remove one or more contacts") style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { confirmAction(); }]]; @@ -482,8 +483,8 @@ + (BOOL)hasSession_alertIfNot { if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { return YES; } else { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"pleaseLogInToYourAccount", @"Alert title shown when you need to log in to continue with the action you want to do") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"pleaseLogInToYourAccount", @"Alert title shown when you need to log in to continue with the action you want to do") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; return NO; } @@ -546,7 +547,7 @@ + (void)deleteUserData { + (void)deleteMasterKey { NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject; - NSString *masterKeyFilePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.txt", NSLocalizedString(@"general.security.recoveryKeyFile", @"Name for the recovery key file")]]; + NSString *masterKeyFilePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.txt", LocalizedString(@"general.security.recoveryKeyFile", @"Name for the recovery key file")]]; [[NSFileManager defaultManager] mnz_removeItemAtPath:masterKeyFilePath]; } @@ -574,12 +575,12 @@ + (void)deletePasscode { + (void)showExportMasterKeyInView:(UIViewController *)viewController completion:(void (^ _Nullable)(void))completion { NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject; - NSString *masterKeyFilePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.txt", NSLocalizedString(@"general.security.recoveryKeyFile", @"Name for the recovery key file")]]; + NSString *masterKeyFilePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.txt", LocalizedString(@"general.security.recoveryKeyFile", @"Name for the recovery key file")]]; BOOL success = [[NSFileManager defaultManager] createFileAtPath:masterKeyFilePath contents:[[[MEGASdkManager sharedMEGASdk] masterKey] dataUsingEncoding:NSUTF8StringEncoding] attributes:@{NSFileProtectionKey:NSFileProtectionComplete}]; if (success) { - UIAlertController *recoveryKeyAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"masterKeyExported", @"Alert title shown when you have exported your MEGA Recovery Key") message:NSLocalizedString(@"masterKeyExported_alertMessage", @"The Recovery Key has been exported into the Offline section as MEGA-RECOVERYKEY.txt. Note: It will be deleted if you log out, please store it in a safe place.") preferredStyle:UIAlertControllerStyleAlert]; - [recoveryKeyAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + UIAlertController *recoveryKeyAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"masterKeyExported", @"Alert title shown when you have exported your MEGA Recovery Key") message:LocalizedString(@"masterKeyExported_alertMessage", @"The Recovery Key has been exported into the Offline section as MEGA-RECOVERYKEY.txt. Note: It will be deleted if you log out, please store it in a safe place.") preferredStyle:UIAlertControllerStyleAlert]; + [recoveryKeyAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [[MEGASdkManager sharedMEGASdk] masterKeyExported]; [viewController dismissViewControllerAnimated:YES completion:^{ if (completion) { @@ -596,8 +597,8 @@ + (void)showMasterKeyCopiedAlert { UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; pasteboard.string = [[MEGASdkManager sharedMEGASdk] masterKey]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"recoveryKeyCopiedToClipboard", @"Title of the dialog displayed when copy the user's Recovery Key to the clipboard to be saved or exported - (String as short as possible).") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"recoveryKeyCopiedToClipboard", @"Title of the dialog displayed when copy the user's Recovery Key to the clipboard to be saved or exported - (String as short as possible).") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; [[MEGASdkManager sharedMEGASdk] masterKeyExported]; @@ -607,13 +608,13 @@ + (void)showMasterKeyCopiedAlert { + (void)enableOrDisableLog { BOOL enableLog = ![[NSUserDefaults standardUserDefaults] boolForKey:@"logging"]; - NSString *alertTitle = enableLog ? NSLocalizedString(@"enableDebugMode_title", @"Alert title shown when the DEBUG mode is enabled") :NSLocalizedString(@"disableDebugMode_title", @"Alert title shown when the DEBUG mode is disabled"); - NSString *alertMessage = enableLog ? NSLocalizedString(@"enableDebugMode_message", @"Alert message shown when the DEBUG mode is enabled") :NSLocalizedString(@"disableDebugMode_message", @"Alert message shown when the DEBUG mode is disabled"); + NSString *alertTitle = enableLog ? LocalizedString(@"enableDebugMode_title", @"Alert title shown when the DEBUG mode is enabled") :LocalizedString(@"disableDebugMode_title", @"Alert title shown when the DEBUG mode is disabled"); + NSString *alertMessage = enableLog ? LocalizedString(@"enableDebugMode_message", @"Alert message shown when the DEBUG mode is enabled") :LocalizedString(@"disableDebugMode_message", @"Alert message shown when the DEBUG mode is disabled"); UIAlertController *logAlertController = [UIAlertController alertControllerWithTitle:alertTitle message:alertMessage preferredStyle:UIAlertControllerStyleAlert]; - [logAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; + [logAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"Button title to cancel something") style:UIAlertActionStyleCancel handler:nil]]; - [logAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to cancel something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [logAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to cancel something") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { if (enableLog) { #if MAIN_APP_TARGET [MEGAChatSdk setLogObject:Logger.shared]; diff --git a/iMEGA/Utils/LiveText/LiveTextImageView.swift b/iMEGA/Utils/LiveText/LiveTextImageView.swift index 9816bcdfea..b8d844832d 100644 --- a/iMEGA/Utils/LiveText/LiveTextImageView.swift +++ b/iMEGA/Utils/LiveText/LiveTextImageView.swift @@ -1,7 +1,7 @@ @preconcurrency import VisionKit @available(iOS 16.0, *) -final class LiveTextImageView: UIImageView { +final class LiveTextImageView: SDAnimatedImageView { private lazy var interaction: ImageAnalysisInteraction = { let interaction = ImageAnalysisInteraction() interaction.preferredInteractionTypes = .automatic diff --git a/iMEGA/Utils/Logs/MEGALogMacros.h b/iMEGA/Utils/Logs/MEGALogMacros.h index 55689aad8e..aec9b6ac0f 100644 --- a/iMEGA/Utils/Logs/MEGALogMacros.h +++ b/iMEGA/Utils/Logs/MEGALogMacros.h @@ -1,4 +1,3 @@ - #import "MEGASdk.h" #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) diff --git a/iMEGA/Utils/Logs/MEGALogger.h b/iMEGA/Utils/Logs/MEGALogger.h index 610778b57c..f28a0df430 100644 --- a/iMEGA/Utils/Logs/MEGALogger.h +++ b/iMEGA/Utils/Logs/MEGALogger.h @@ -1,4 +1,3 @@ - #import #import "MEGASdk.h" #import "MEGAChatSdk.h" diff --git a/iMEGA/Utils/Logs/MEGALogger.m b/iMEGA/Utils/Logs/MEGALogger.m index bebb26932b..bf9f2cbd46 100644 --- a/iMEGA/Utils/Logs/MEGALogger.m +++ b/iMEGA/Utils/Logs/MEGALogger.m @@ -1,4 +1,3 @@ - #import "MEGALogger.h" #import "UIDevice+MNZCategory.h" diff --git a/iMEGA/Utils/MEGAConstants.h b/iMEGA/Utils/MEGAConstants.h index c8f71c8c5f..a490a37d16 100644 --- a/iMEGA/Utils/MEGAConstants.h +++ b/iMEGA/Utils/MEGAConstants.h @@ -1,4 +1,3 @@ - #import #pragma mark - global constants diff --git a/iMEGA/Utils/MEGAConstants.m b/iMEGA/Utils/MEGAConstants.m index c215a0a5c6..b6f8cda9c0 100644 --- a/iMEGA/Utils/MEGAConstants.m +++ b/iMEGA/Utils/MEGAConstants.m @@ -1,4 +1,3 @@ - #import "MEGAConstants.h" #pragma mark - global constants diff --git a/iMEGA/Utils/MEGALinkManager+Additions.swift b/iMEGA/Utils/MEGALinkManager+Additions.swift index 1059901a1e..cedb83fe66 100644 --- a/iMEGA/Utils/MEGALinkManager+Additions.swift +++ b/iMEGA/Utils/MEGALinkManager+Additions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPermissions import MEGAPresentation import MEGASdk @@ -37,15 +38,20 @@ extension MEGALinkManager { MEGALinkManager.showLinkNotValid() return } + let sdk: MEGASdk = .sharedSdk + let nodeProvider: some PublicAlbumNodeProviderProtocol = PublicAlbumNodeProvider.shared let userAlbumRepository = UserAlbumRepository.newRepo let shareAlbumRepository = ShareAlbumRepository( - sdk: MEGASdk.shared, - publicAlbumNodeProvider: PublicAlbumNodeProvider.shared) + sdk: sdk, + publicAlbumNodeProvider: nodeProvider) + + let nodeRepository: NodeRepository = .newRepo + let importAlbumUseCase = ImportPublicAlbumUseCase( saveAlbumToFolderUseCase: SaveAlbumToFolderUseCase( nodeActionRepository: NodeActionRepository.newRepo, shareAlbumRepository: shareAlbumRepository, - nodeRepository: NodeRepository.newRepo), + nodeRepository: nodeRepository), userAlbumRepository: userAlbumRepository) let vm = ImportAlbumViewModel( @@ -58,9 +64,19 @@ extension MEGALinkManager { importPublicAlbumUseCase: importAlbumUseCase, accountUseCase: AccountUseCase( repository: AccountRepository.newRepo), - tracker: DIContainer.tracker) + saveMediaUseCase: SaveMediaToPhotosUseCase( + downloadFileRepository: DownloadFileRepository( + sdk: sdk, + nodeProvider: nodeProvider), + fileCacheRepository: FileCacheRepository.newRepo, + nodeRepository: nodeRepository), + transferWidgetResponder: TransfersWidgetViewController.sharedTransfer(), + permissionHandler: DevicePermissionsHandler.makeHandler(), + tracker: DIContainer.tracker, + monitorUseCase: NetworkMonitorUseCase(repo: NetworkMonitorRepository())) - let viewController = UIHostingController(dismissibleView: ImportAlbumView(viewModel: vm)) + let viewController = UIHostingController(dismissibleView: ImportAlbumView( + viewModel: vm)) viewController.modalPresentationStyle = .fullScreen UIApplication.mnz_visibleViewController().present(viewController, animated: true) } @@ -94,7 +110,7 @@ extension MEGALinkManager { // does it make sense to handle this error apart from early return? return } - let notificationText = String(format: NSLocalizedString("You have joined %@", comment: "Text shown in a notification to let the user know that has joined a public chat room after login or account creation"), chatTitle) + let notificationText = String(format: Strings.localized("You have joined %@", comment: "Text shown in a notification to let the user know that has joined a public chat room after login or account creation"), chatTitle) if shouldAskForNotificationsPermissions { SVProgressHUD.showSuccess(withStatus: notificationText) @@ -156,13 +172,22 @@ extension MEGALinkManager { ) } - @objc class func shouldOpenWaitingRoom(chatRoom: MEGAChatRoom) -> Bool { - let isModerator = chatRoom.ownPrivilege.toOwnPrivilegeEntity() == .moderator - return !isModerator && chatRoom.isWaitingRoomEnabled && DIContainer.featureFlagProvider.isFeatureFlagEnabled(for: .waitingRoom) + @objc class func hasActiveMeeting(for request: MEGAChatRequest) -> Bool { + let list = request.megaHandleList + guard let list else { return false } + return list.size > 0 && list.megaHandle(at: 0) != 0 + } + + @objc class func shouldOpenWaitingRoom(forChatOptions bitMask: Int) -> Bool { + MEGAChatSdk.shared.hasChatOptionEnabled(for: .waitingRoom, chatOptionsBitMask: bitMask) } - @objc class func openWaitingRoom(for chatId: ChatIdEntity) { + @objc class func openWaitingRoom(for chatId: ChatIdEntity, chatLink: String) { guard let scheduledMeeting = MEGAChatSdk.shared.scheduledMeetings(byChat: chatId).first?.toScheduledMeetingEntity() else { return } - WaitingRoomViewRouter(presenter: UIApplication.mnz_visibleViewController(), scheduledMeeting: scheduledMeeting).start() + WaitingRoomViewRouter( + presenter: UIApplication.mnz_visibleViewController(), + scheduledMeeting: scheduledMeeting, + chatLink: chatLink + ).start() } } diff --git a/iMEGA/Utils/MEGALinkManager.h b/iMEGA/Utils/MEGALinkManager.h index cbb97ed154..0b86ef1ac8 100644 --- a/iMEGA/Utils/MEGALinkManager.h +++ b/iMEGA/Utils/MEGALinkManager.h @@ -1,4 +1,3 @@ - #import #import "LinkOption.h" diff --git a/iMEGA/Utils/MEGALinkManager.m b/iMEGA/Utils/MEGALinkManager.m index a89779a64e..ac656de4a3 100644 --- a/iMEGA/Utils/MEGALinkManager.m +++ b/iMEGA/Utils/MEGALinkManager.m @@ -1,4 +1,3 @@ - #import "MEGALinkManager.h" #import @@ -37,6 +36,8 @@ #import "UnavailableLinkView.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + static NSURL *linkURL; static NSURL *secondaryLinkURL; static URLType urlType; @@ -166,12 +167,12 @@ + (void)processSelectedOptionOnLink { if (error.type != MEGAErrorTypeApiOk && error.type != MEGAErrorTypeApiEExist) { if (error.type == MEGAChatErrorTypeNoEnt) { [SVProgressHUD dismiss]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Chat Link Unavailable", @"Shown when an invalid/inexisting/not-available-anymore chat link is opened.") message:NSLocalizedString(@"This chat link is no longer available", @"Shown when an inexisting/unavailable/removed link is tried to be opened.") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"Chat Link Unavailable", @"Shown when an invalid/inexisting/not-available-anymore chat link is opened.") message:LocalizedString(@"This chat link is no longer available", @"Shown when an inexisting/unavailable/removed link is tried to be opened.") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; } else { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; } return; } @@ -239,8 +240,8 @@ + (void)presentNode { } } else { if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { - UIAlertController *theContentIsNotAvailableAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"theContentIsNotAvailableForThisAccount", @"") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [theContentIsNotAvailableAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *theContentIsNotAvailableAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"theContentIsNotAvailableForThisAccount", @"") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [theContentIsNotAvailableAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:theContentIsNotAvailableAlertController animated:YES completion:nil]; } @@ -309,8 +310,8 @@ + (void)processLinkURL:(NSURL *)url { if ([SAMKeychain passwordForService:@"MEGA" account:@"sessionV3"]) { [MEGALinkManager resetLinkAndURLType]; - UIAlertController *alreadyLoggedInAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"error", nil) message:NSLocalizedString(@"This link is not related to this account. Please log in with the correct account.", @"Error message shown when opening a link with an account that not corresponds to the link") preferredStyle:UIAlertControllerStyleAlert]; - [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDestructive handler:nil]]; + UIAlertController *alreadyLoggedInAlertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"error", @"") message:LocalizedString(@"This link is not related to this account. Please log in with the correct account.", @"Error message shown when opening a link with an account that not corresponds to the link") preferredStyle:UIAlertControllerStyleAlert]; + [alreadyLoggedInAlertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"Button title to accept something") style:UIAlertActionStyleDestructive handler:nil]]; [UIApplication.mnz_visibleViewController presentViewController:alreadyLoggedInAlertController animated:YES completion:nil]; } else { @@ -333,8 +334,8 @@ + (void)processLinkURL:(NSURL *)url { MEGAQueryRecoveryLinkRequestDelegate *queryRecoveryLinkRequestDelegate = [[MEGAQueryRecoveryLinkRequestDelegate alloc] initWithRequestCompletion:nil urlType:URLTypeChangeEmailLink]; [[MEGASdkManager sharedMEGASdk] queryChangeEmailLink:url.mnz_MEGAURL delegate:queryRecoveryLinkRequestDelegate]; } else { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"needToBeLoggedInToCompleteYourEmailChange", @"Error message when a user attempts to change their email without an active login session.") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"needToBeLoggedInToCompleteYourEmailChange", @"Error message when a user attempts to change their email without an active login session.") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; } @@ -502,7 +503,7 @@ + (void)showLinkNotValid { UIViewController *viewController = [[UIViewController alloc] init]; [viewController.view addSubview:unavailableLinkView]; - viewController.navigationItem.title = NSLocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid"); + viewController.navigationItem.title = LocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid"); MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:viewController]; [navigationController addRightCancelButton]; @@ -537,27 +538,27 @@ + (void)showEncryptedLinkAlert:(NSString *)encryptedLinkURLString { MEGALinkManager.linkURL = [NSURL URLWithString:request.text]; [MEGALinkManager processLinkURL:[NSURL URLWithString:request.text]]; } onError:^(MEGARequest *request) { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"To access this link, you will need its password.", @"This dialog message is used on the Password Decrypt dialog. The link is a password protected link so the user needs to enter the password to decrypt the link.") message:nil preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"To access this link, you will need its password.", @"This dialog message is used on the Password Decrypt dialog. The link is a password protected link so the user needs to enter the password to decrypt the link.") message:nil preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { [MEGALinkManager showEncryptedLinkAlert:request.link]; }]]; [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; }]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"To access this link, you will need its password.", @"This dialog message is used on the Password Decrypt dialog. The link is a password protected link so the user needs to enter the password to decrypt the link.") message:NSLocalizedString(@"If you do not have the password, contact the creator of the link.", @"This dialog message is used on the Password Decrypt dialog as an instruction for the user.") preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"To access this link, you will need its password.", @"This dialog message is used on the Password Decrypt dialog. The link is a password protected link so the user needs to enter the password to decrypt the link.") message:LocalizedString(@"If you do not have the password, contact the creator of the link.", @"This dialog message is used on the Password Decrypt dialog as an instruction for the user.") preferredStyle:UIAlertControllerStyleAlert]; [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { - textField.placeholder = NSLocalizedString(@"Enter the password", @"This placeholder text is used on the Password Decrypt dialog as an instruction for the user."); + textField.placeholder = LocalizedString(@"Enter the password", @"This placeholder text is used on the Password Decrypt dialog as an instruction for the user."); [textField addTarget:self action:@selector(alertTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; textField.shouldReturnCompletion = ^BOOL(UITextField *textField) { return !textField.text.mnz_isEmpty; }; textField.secureTextEntry = YES; }]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [[MEGASdkManager sharedMEGASdk] decryptPasswordProtectedLink:encryptedLinkURLString password:alertController.textFields.firstObject.text delegate:delegate]; }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { [MEGALinkManager resetLinkAndURLType]; MEGALinkManager.secondaryLinkURL = nil; }]]; @@ -672,7 +673,7 @@ + (void)handleContactLink { [NSUserDefaults.standardUserDefaults setInteger:AffiliateTypeContact forKey:MEGALastPublicTypeAccessed]; [NSUserDefaults.standardUserDefaults setDouble:NSDate.date.timeIntervalSince1970 forKey:MEGALastPublicTimestampAccessed]; } onError:^(MEGAError *error) { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid")]; + [SVProgressHUD showErrorWithStatus:LocalizedString(@"linkNotValid", @"Message shown when the user clicks on an link that is not valid")]; }]; [[MEGASdkManager sharedMEGASdk] contactLinkQueryWithHandle:handle delegate:delegate]; @@ -705,13 +706,13 @@ + (void)presentInviteModalForEmail:(NSString *)email fullName:(NSString *)fullNa MEGAUser *user = [[MEGASdkManager sharedMEGASdk] contactForEmail:email]; if (user && user.visibility == MEGAUserVisibilityVisible) { - inviteOrDismissModal.detail = [NSLocalizedString(@"alreadyAContact", @"Error message displayed when trying to invite a contact who is already added.") stringByReplacingOccurrencesOfString:@"%s" withString:email]; - inviteOrDismissModal.firstButtonTitle = NSLocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); + inviteOrDismissModal.detail = [LocalizedString(@"alreadyAContact", @"Error message displayed when trying to invite a contact who is already added.") stringByReplacingOccurrencesOfString:@"%s" withString:email]; + inviteOrDismissModal.firstButtonTitle = LocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); inviteOrDismissModal.firstCompletion = dismissCompletion; } else { BOOL isInOutgoingContactRequest = NO; MEGAContactRequestList *outgoingContactRequestList = [[MEGASdkManager sharedMEGASdk] outgoingContactRequests]; - for (NSInteger i = 0; i < outgoingContactRequestList.size.integerValue; i++) { + for (NSInteger i = 0; i < outgoingContactRequestList.size; i++) { MEGAContactRequest *contactRequest = [outgoingContactRequestList contactRequestAtIndex:i]; if ([email isEqualToString:contactRequest.targetEmail]) { isInOutgoingContactRequest = YES; @@ -720,17 +721,17 @@ + (void)presentInviteModalForEmail:(NSString *)email fullName:(NSString *)fullNa } if (isInOutgoingContactRequest) { inviteOrDismissModal.image = [UIImage imageNamed:@"inviteSent"]; - inviteOrDismissModal.viewTitle = NSLocalizedString(@"inviteSent", @"Title shown when the user sends a contact invitation"); - NSString *detailText = NSLocalizedString(@"dialog.inviteContact.outgoingContactRequest", @"Detail message shown when a contact has been invited. The [X] placeholder will be replaced on runtime for the email of the invited user"); + inviteOrDismissModal.viewTitle = LocalizedString(@"inviteSent", @"Title shown when the user sends a contact invitation"); + NSString *detailText = LocalizedString(@"dialog.inviteContact.outgoingContactRequest", @"Detail message shown when a contact has been invited. The [X] placeholder will be replaced on runtime for the email of the invited user"); detailText = [detailText stringByReplacingOccurrencesOfString:@"[X]" withString:email]; inviteOrDismissModal.detail = detailText; inviteOrDismissModal.boldInDetail = email; - inviteOrDismissModal.firstButtonTitle = NSLocalizedString(@"close", nil); + inviteOrDismissModal.firstButtonTitle = LocalizedString(@"close", @""); inviteOrDismissModal.firstCompletion = dismissCompletion; } else { inviteOrDismissModal.detail = email; - inviteOrDismissModal.firstButtonTitle = NSLocalizedString(@"invite", @"A button on a dialog which invites a contact to join MEGA."); - inviteOrDismissModal.dismissButtonTitle = NSLocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); + inviteOrDismissModal.firstButtonTitle = LocalizedString(@"invite", @"A button on a dialog which invites a contact to join MEGA."); + inviteOrDismissModal.dismissButtonTitle = LocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible)."); inviteOrDismissModal.firstCompletion = firstCompletion; inviteOrDismissModal.dismissCompletion = dismissCompletion; } @@ -748,12 +749,12 @@ + (void)handlePublicChatLink { if (error.type != MEGAErrorTypeApiOk && error.type != MEGAErrorTypeApiEExist) { if (error.type == MEGAChatErrorTypeNoEnt) { [SVProgressHUD dismiss]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Chat Link Unavailable", @"Shown when an invalid/inexisting/not-available-anymore chat link is opened.") message:NSLocalizedString(@"This chat link is no longer available", @"Shown when an inexisting/unavailable/removed link is tried to be opened.") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"Chat Link Unavailable", @"Shown when an invalid/inexisting/not-available-anymore chat link is opened.") message:LocalizedString(@"This chat link is no longer available", @"Shown when an inexisting/unavailable/removed link is tried to be opened.") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; } else { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; } return; } @@ -771,7 +772,7 @@ + (void)handlePublicChatLink { MEGAChatGenericRequestDelegate *autorejoinPublicChatDelegate = [[MEGAChatGenericRequestDelegate alloc] initWithCompletion:^(MEGAChatRequest * _Nonnull request, MEGAChatError * _Nonnull error) { if (error.type) { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; return; } [MEGALinkManager.joiningOrLeavingChatBase64Handles removeObject:[MEGASdk base64HandleForUserHandle:request.chatHandle]]; @@ -802,17 +803,17 @@ + (void)handlePublicChatLink { if (error.type != MEGAErrorTypeApiOk && error.type != MEGAErrorTypeApiEExist) { if (error.type == MEGAChatErrorTypeNoEnt) { [SVProgressHUD dismiss]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Chat Link Unavailable", @"Shown when an invalid/inexisting/not-available-anymore chat link is opened.") message:NSLocalizedString(@"This chat link is no longer available", @"Shown when an inexisting/unavailable/removed link is tried to be opened.") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"Chat Link Unavailable", @"Shown when an invalid/inexisting/not-available-anymore chat link is opened.") message:LocalizedString(@"This chat link is no longer available", @"Shown when an inexisting/unavailable/removed link is tried to be opened.") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; } else if (error.type == MEGAChatErrorTypeArgs) { [SVProgressHUD dismiss]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"meetings.joinMeeting.header", @"") message:NSLocalizedString(@"meetings.joinMeeting.description", @"") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"meetings.joinMeeting.header", @"") message:LocalizedString(@"meetings.joinMeeting.description", @"") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleCancel handler:nil]]; [UIApplication.mnz_visibleViewController presentViewController:alertController animated:YES completion:nil]; } else { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; } return; } @@ -826,27 +827,24 @@ + (void)handlePublicChatLink { } + (void)openMeetingWithRequest:(MEGAChatRequest * _Nonnull)request chatLinkURL:(NSURL * _Nonnull)chatLinkUrl { - // "It's a meeting, open join call" - MEGAHandleList *list = request.megaHandleList; - MEGAChatRoom *chatRoom = [[MEGAChatSdk shared] chatRoomForChatId:request.chatHandle]; - - if (list.size > 0 && [list megaHandleAtIndex:0] != 0) { - [self createMeetingAndShow:request.chatHandle userhandle:request.userHandle publicChatLink:chatLinkUrl]; + if ([self shouldOpenWaitingRoomForChatOptions:request.privilege]) { [SVProgressHUD dismiss]; - } else if ([self shouldOpenWaitingRoomWithChatRoom: chatRoom]) { + [self openWaitingRoomFor:request.chatHandle chatLink:chatLinkUrl.absoluteString]; + } else if ([self hasActiveMeetingFor:request]) { + [self createMeetingAndShow:request.chatHandle userhandle:request.userHandle publicChatLink:chatLinkUrl]; [SVProgressHUD dismiss]; - [self openWaitingRoomFor:request.chatHandle]; } else { // meeting ended [SVProgressHUD dismiss]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"meetings.alert.end", nil) message:NSLocalizedString(@"meetings.alert.end.description", @"Shown when an inexisting/unavailable/removed link is tried to be opened.") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"meetings.alert.meetingchat", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + MEGAChatRoom *chatRoom = [[MEGAChatSdk shared] chatRoomForChatId:request.chatHandle]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"meetings.alert.end", @"") message:LocalizedString(@"meetings.alert.end.description", @"Shown when an inexisting/unavailable/removed link is tried to be opened.") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"meetings.alert.meetingchat", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { if (!chatRoom.isPreview && !chatRoom.isActive) { MEGAChatGenericRequestDelegate *autorejoinPublicChatDelegate = [[MEGAChatGenericRequestDelegate alloc] initWithCompletion:^(MEGAChatRequest * _Nonnull request, MEGAChatError * _Nonnull error) { if (error.type) { [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeNone]; - [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, NSLocalizedString(error.name, nil)]]; + [SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:@"%@ %@", request.requestString, LocalizedString(error.name, @"")]]; return; } [MEGALinkManager.joiningOrLeavingChatBase64Handles removeObject:[MEGASdk base64HandleForUserHandle:request.chatHandle]]; @@ -858,7 +856,7 @@ + (void)openMeetingWithRequest:(MEGAChatRequest * _Nonnull)request chatLinkURL: [MEGALinkManager createChatAndShow:request.chatHandle publicChatLink:chatLinkUrl]; } }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { if ([MEGASdkManager sharedMEGAChatSdk].myEmail == nil || [MEGASdkManager sharedMEGAChatSdk].myEmail.mnz_isEmpty) { [MEGASdkManager.sharedMEGAChatSdk logout]; [[[EncourageGuestUserToJoinMegaRouter alloc] initWithPresenter:UIApplication.mnz_visibleViewController] start]; diff --git a/iMEGA/Utils/MEGALocalNotificationManager.h b/iMEGA/Utils/MEGALocalNotificationManager.h index bdd0e2ccf6..c8c4a540fb 100644 --- a/iMEGA/Utils/MEGALocalNotificationManager.h +++ b/iMEGA/Utils/MEGALocalNotificationManager.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/MEGALocalNotificationManager.m b/iMEGA/Utils/MEGALocalNotificationManager.m index 3a8f489099..0872ba0556 100644 --- a/iMEGA/Utils/MEGALocalNotificationManager.m +++ b/iMEGA/Utils/MEGALocalNotificationManager.m @@ -1,4 +1,3 @@ - #import "MEGALocalNotificationManager.h" #ifndef MNZ_APP_EXTENSION @@ -13,6 +12,8 @@ #import "MEGAStore.h" #import "NSString+MNZCategory.h" +@import MEGAL10nObjc; + #ifdef MNZ_NOTIFICATION_EXTENSION #import "MEGANotifications-Swift.h" #else @@ -165,11 +166,11 @@ - (NSString *)bodyString { } } else { MEGALogWarning(@"[Notification] Attachment message wiht nodelist with more than one node is not expected"); - body = [NSString stringWithFormat:NSLocalizedString(@"files", nil), nodeList.size.integerValue]; + body = [NSString stringWithFormat:LocalizedString(@"files", @""), nodeList.size.integerValue]; } } else { MEGALogError(@"[Notification] Attachment message without nodelist"); - body = NSLocalizedString(@"Not found", nil); + body = LocalizedString(@"Not found", @""); } } break; @@ -189,7 +190,7 @@ - (NSString *)bodyString { case MEGAChatMessageTypeContainsMeta: if (self.message.containsMeta.type == MEGAChatContainsMetaTypeGeolocation) { - body = [NSString stringWithFormat:@"📍 %@", NSLocalizedString(@"Pinned Location", @"Text shown in location-type messages")]; + body = [NSString stringWithFormat:@"📍 %@", LocalizedString(@"Pinned Location", @"Text shown in location-type messages")]; } else { body = self.message.content; } @@ -197,7 +198,7 @@ - (NSString *)bodyString { case MEGAChatMessageTypeNormal: if (self.message.isEdited) { - body = [NSString stringWithFormat:@"%@ %@", self.message.content, NSLocalizedString(@"edited", nil)]; + body = [NSString stringWithFormat:@"%@ %@", self.message.content, LocalizedString(@"edited", @"")]; } else { body = self.message.content; } @@ -210,7 +211,7 @@ - (NSString *)bodyString { [sharedUserDefaults setInteger:badgeCount + 1 forKey:MEGAApplicationIconBadgeNumber]; UIApplication.sharedApplication.applicationIconBadgeNumber = badgeCount + 1; #endif - body = NSLocalizedString(@"missedCall", @"Title of the notification for a missed call"); + body = LocalizedString(@"missedCall", @"Title of the notification for a missed call"); break; } diff --git a/iMEGA/Utils/MEGANavigationController.m b/iMEGA/Utils/MEGANavigationController.m index fc595f6647..01367d8948 100644 --- a/iMEGA/Utils/MEGANavigationController.m +++ b/iMEGA/Utils/MEGANavigationController.m @@ -1,5 +1,5 @@ - #import "MEGANavigationController.h" +@import MEGAL10nObjc; #ifdef MNZ_SHARE_EXTENSION #import "MEGAShare-Swift.h" @@ -56,7 +56,7 @@ - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)ani #pragma mark - Private - (UIBarButtonItem *)cancelBarButtonItem { - UIBarButtonItem *cancelBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"cancel", nil) style:UIBarButtonItemStylePlain target:nil action:@selector(dismissNavigationController)]; + UIBarButtonItem *cancelBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:LocalizedString(@"cancel", @"") style:UIBarButtonItemStylePlain target:nil action:@selector(dismissNavigationController)]; return cancelBarButtonItem; } diff --git a/iMEGA/Utils/MEGAProcessAsset.h b/iMEGA/Utils/MEGAProcessAsset.h index dd3606ad46..a4acef48d5 100644 --- a/iMEGA/Utils/MEGAProcessAsset.h +++ b/iMEGA/Utils/MEGAProcessAsset.h @@ -1,4 +1,3 @@ - #import #import diff --git a/iMEGA/Utils/MEGAProcessAsset.m b/iMEGA/Utils/MEGAProcessAsset.m index c6ec2f1433..463ec45055 100644 --- a/iMEGA/Utils/MEGAProcessAsset.m +++ b/iMEGA/Utils/MEGAProcessAsset.m @@ -1,4 +1,3 @@ - #import "MEGAProcessAsset.h" #import "ChatVideoUploadQuality.h" @@ -11,6 +10,8 @@ #import "UIApplication+MNZCategory.h" #import "NSString+MNZCategory.h" +@import MEGAL10nObjc; + #ifdef MNZ_SHARE_EXTENSION #import "MEGAShare-Swift.h" #else @@ -150,9 +151,9 @@ - (void)prepare { dispatch_async(dispatch_get_main_queue(), ^(void) { [self.alertController dismissViewControllerAnimated:YES completion:^{ if (self.exportAssetFailed) { - NSString *message = NSLocalizedString(@"shareExtensionUnsupportedAssets", @"Inform user that there were unsupported assets in the share extension."); + NSString *message = LocalizedString(@"shareExtensionUnsupportedAssets", @"Inform user that there were unsupported assets in the share extension."); UIAlertController *videoExportFailedController = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]; - [videoExportFailedController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDestructive handler:nil]]; + [videoExportFailedController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDestructive handler:nil]]; [UIApplication.mnz_presentingViewController presentViewController:videoExportFailedController animated:YES completion:nil]; } }]; @@ -392,9 +393,9 @@ - (void)prepareAVAsset { SDAVAssetExportSession *encoder = [self configureEncoderWithAVAsset:self.avAsset videoQuality:videoQuality filePath:filePath]; if (!self.alertController) { - NSString *title = [NSLocalizedString(@"preparing...", @"Label for the status of a transfer when is being preparing - (String as short as possible.") stringByAppendingString:@"\n"]; + NSString *title = [LocalizedString(@"preparing...", @"Label for the status of a transfer when is being preparing - (String as short as possible.") stringByAppendingString:@"\n"]; self.alertController = [UIAlertController alertControllerWithTitle:title message:@"\n" preferredStyle:UIAlertControllerStyleAlert]; - [self.alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + [self.alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { MEGALogDebug(@"[PA] User cancelled the export session"); [encoder cancelExport]; NSDictionary *dict = @{NSLocalizedDescriptionKey:@"User cancels export"}; @@ -455,7 +456,7 @@ - (BOOL)hasFreeSpaceOnDiskForWriteFile:(long long)fileSize { uint64_t freeSpace = NSFileManager.defaultManager.mnz_fileSystemFreeSize; if (fileSize > freeSpace) { - NSDictionary *dict = @{NSLocalizedDescriptionKey:NSLocalizedString(@"nodeTooBig", @"Title shown inside an alert if you don't have enough space on your device to download something")}; + NSDictionary *dict = @{NSLocalizedDescriptionKey:LocalizedString(@"nodeTooBig", @"Title shown inside an alert if you don't have enough space on your device to download something")}; NSError *error = [NSError errorWithDomain:MEGAProcessAssetErrorDomain code:-2 userInfo:dict]; if (self.error) { self.error(error); @@ -689,9 +690,9 @@ - (SDAVAssetExportSession *)configureEncoderWithAVAsset:(AVAsset *)avAsset video - (void)downscaleVideoAsset:(PHAsset *)asset encoder:(SDAVAssetExportSession *)encoder { if (!self.alertController) { - NSString *title = [NSLocalizedString(@"preparing...", @"Label for the status of a transfer when is being preparing - (String as short as possible.") stringByAppendingString:@"\n"]; + NSString *title = [LocalizedString(@"preparing...", @"Label for the status of a transfer when is being preparing - (String as short as possible.") stringByAppendingString:@"\n"]; self.alertController = [UIAlertController alertControllerWithTitle:title message:@"\n" preferredStyle:UIAlertControllerStyleAlert]; - [self.alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + [self.alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { MEGALogDebug(@"[PA] User cancelled the export session"); self.cancelExportByUser = YES; [self exportSessionCancelledOrFailed]; diff --git a/iMEGA/Utils/MEGAReachabilityManager+Additions.swift b/iMEGA/Utils/MEGAReachabilityManager+Additions.swift index e7560df5f7..d6fc6d1b8a 100644 --- a/iMEGA/Utils/MEGAReachabilityManager+Additions.swift +++ b/iMEGA/Utils/MEGAReachabilityManager+Additions.swift @@ -1,4 +1,3 @@ - extension MEGAReachabilityManager { static func statusConnectionMessage() -> String { if MEGAReachabilityManager.isReachable() { diff --git a/iMEGA/Utils/MEGAReachabilityManager.h b/iMEGA/Utils/MEGAReachabilityManager.h index 4f563a1bbf..fc22653863 100644 --- a/iMEGA/Utils/MEGAReachabilityManager.h +++ b/iMEGA/Utils/MEGAReachabilityManager.h @@ -1,4 +1,3 @@ - #import #import "Reachability.h" diff --git a/iMEGA/Utils/MEGAReachabilityManager.m b/iMEGA/Utils/MEGAReachabilityManager.m index 02a3b51400..a5c79915e3 100644 --- a/iMEGA/Utils/MEGAReachabilityManager.m +++ b/iMEGA/Utils/MEGAReachabilityManager.m @@ -1,4 +1,3 @@ - #import "MEGAReachabilityManager.h" #import @@ -10,6 +9,8 @@ #import "UIApplication+MNZCategory.h" #import "MEGASdkManager.h" +@import MEGAL10nObjc; + @interface MEGAReachabilityManager () @property (nonatomic, strong) Reachability *reachability; @@ -77,7 +78,7 @@ + (BOOL)isReachableHUDIfNot { case kCTCellularDataRestrictedStateUnknown: case kCTCellularDataNotRestricted: #if defined(SV_APP_EXTENSIONS) || defined(MAIN_APP_TARGET) - [SVProgressHUD showImage:[UIImage imageNamed:@"hudForbidden"] status:NSLocalizedString(@"noInternetConnection", @"Text shown on the app when you don't have connection to the internet or when you have lost it")]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudForbidden"] status:LocalizedString(@"noInternetConnection", @"Text shown on the app when you don't have connection to the internet or when you have lost it")]; #endif break; } @@ -206,11 +207,11 @@ - (void)recordMobileDataState:(CTCellularDataRestrictedState)state { - (void)mobileDataIsTurnedOffAlert { #ifdef MAIN_APP_TARGET - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings.") message:NSLocalizedString(@"You can turn on mobile data for this app in Settings.", @"Extra information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings.") preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"settingsTitle", @"Title of the Settings section") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:LocalizedString(@"Mobile Data is turned off", @"Information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings.") message:LocalizedString(@"You can turn on mobile data for this app in Settings.", @"Extra information shown when the user has disabled the 'Mobile Data' setting for MEGA in the iOS Settings.") preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"settingsTitle", @"Title of the Settings section") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [UIApplication.sharedApplication openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil]; }]]; - [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:nil]]; + [alertController addAction:[UIAlertAction actionWithTitle:LocalizedString(@"ok", @"") style:UIAlertActionStyleDefault handler:nil]]; dispatch_async(dispatch_get_main_queue(), ^{ [UIApplication.mnz_presentingViewController presentViewController:alertController animated:YES completion:nil]; diff --git a/iMEGA/Utils/MEGASDK/MEGASdkManager.m b/iMEGA/Utils/MEGASDK/MEGASdkManager.m index 11d987533b..305387e774 100644 --- a/iMEGA/Utils/MEGASDK/MEGASdkManager.m +++ b/iMEGA/Utils/MEGASDK/MEGASdkManager.m @@ -1,4 +1,3 @@ - #import "MEGASdkManager.h" #ifdef MNZ_SHARE_EXTENSION diff --git a/iMEGA/Utils/Network/Models/ResponseModel.swift b/iMEGA/Utils/Network/Models/ResponseModel.swift index 0d3680a22b..5f93056a91 100644 --- a/iMEGA/Utils/Network/Models/ResponseModel.swift +++ b/iMEGA/Utils/Network/Models/ResponseModel.swift @@ -36,7 +36,7 @@ struct ResponseModel: Decodable { } var request: RequestModel? - public init(from decoder: Decoder) throws { + public init(from decoder: any Decoder) throws { let keyedContainer = try decoder.container(keyedBy: CodingKeys.self) data = try? keyedContainer.decode([T].self, forKey: CodingKeys.data) diff --git a/iMEGA/Utils/Network/Services/Giphy/GiphyResponseModel.swift b/iMEGA/Utils/Network/Services/Giphy/GiphyResponseModel.swift index 3145446f82..e389e15fa9 100644 --- a/iMEGA/Utils/Network/Services/Giphy/GiphyResponseModel.swift +++ b/iMEGA/Utils/Network/Services/Giphy/GiphyResponseModel.swift @@ -24,7 +24,7 @@ struct GiphyResponseModel: Decodable { case mp4_size } - init(from decoder: Decoder) throws { + init(from decoder: any Decoder) throws { let keyedContainer = try decoder.container(keyedBy: CodingKeys.self) title = (try? keyedContainer.decode(String.self, forKey: CodingKeys.title)) ?? "" let images = try? keyedContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.images) diff --git a/iMEGA/Utils/NodeAccess/Camera Uploads/CameraUploadNodeAccess.swift b/iMEGA/Utils/NodeAccess/Camera Uploads/CameraUploadNodeAccess.swift index 05a2184b35..18be468fe9 100644 --- a/iMEGA/Utils/NodeAccess/Camera Uploads/CameraUploadNodeAccess.swift +++ b/iMEGA/Utils/NodeAccess/Camera Uploads/CameraUploadNodeAccess.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n final class CameraUploadNodeAccess: NodeAccess { @objc static let shared = CameraUploadNodeAccess( @@ -6,10 +7,10 @@ final class CameraUploadNodeAccess: NodeAccess { autoCreate: { CameraUploadManager.isCameraUploadEnabled }, updateInMemoryNotificationName: .MEGACameraUploadTargetFolderUpdatedInMemory, updateInRemoteNotificationName: .MEGACameraUploadTargetFolderChangedInRemote, - loadNodeRequest: MEGASdkManager.sharedMEGASdk().getCameraUploadsFolder, - setNodeRequest: MEGASdkManager.sharedMEGASdk().setCameraUploadsFolderWithHandle, + loadNodeRequest: MEGASdk.shared.getCameraUploadsFolder, + setNodeRequest: MEGASdk.shared.setCameraUploadsFolderWithHandle, nodeName: Strings.Localizable.cameraUploadsLabel, - createNodeRequest: MEGASdkManager.sharedMEGASdk().createFolder + createNodeRequest: MEGASdk.shared.createFolder ) ) } diff --git a/iMEGA/Utils/NodeAccess/MyChatFiles/MyChatFilesFolderNodeAccess.swift b/iMEGA/Utils/NodeAccess/MyChatFiles/MyChatFilesFolderNodeAccess.swift index 3854baa496..8aee746f5d 100644 --- a/iMEGA/Utils/NodeAccess/MyChatFiles/MyChatFilesFolderNodeAccess.swift +++ b/iMEGA/Utils/NodeAccess/MyChatFiles/MyChatFilesFolderNodeAccess.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n final class MyChatFilesFolderNodeAccess: NodeAccess { @objc static let shared = MyChatFilesFolderNodeAccess( @@ -6,10 +7,10 @@ final class MyChatFilesFolderNodeAccess: NodeAccess { autoCreate: { false }, updateInMemoryNotificationName: .MEGAMyChatFilesFolderUpdatedInMemory, updateInRemoteNotificationName: .MEGAMyChatFilesFolderUpdatedInRemote, - loadNodeRequest: MEGASdkManager.sharedMEGASdk().getMyChatFilesFolder, - setNodeRequest: MEGASdkManager.sharedMEGASdk().setMyChatFilesFolderWithHandle, + loadNodeRequest: MEGASdk.shared.getMyChatFilesFolder, + setNodeRequest: MEGASdk.shared.setMyChatFilesFolderWithHandle, nodeName: Strings.Localizable.myChatFiles, - createNodeRequest: MEGASdkManager.sharedMEGASdk().createFolder)) + createNodeRequest: MEGASdk.shared.createFolder)) @objc func updateAutoCreate(status: @escaping @autoclosure () -> Bool) { MyChatFilesFolderNodeAccess.shared.nodeAccessConfiguration.autoCreate = status diff --git a/iMEGA/Utils/NodeAccess/NodeAccess.swift b/iMEGA/Utils/NodeAccess/NodeAccess.swift index 6df1985e48..abd511dd0a 100644 --- a/iMEGA/Utils/NodeAccess/NodeAccess.swift +++ b/iMEGA/Utils/NodeAccess/NodeAccess.swift @@ -2,7 +2,7 @@ import Foundation import MEGADomain import MEGASDKRepo -typealias NodeLoadCompletion = (_ node: MEGANode?, _ error: Error?) -> Void +typealias NodeLoadCompletion = (_ node: MEGANode?, _ error: (any Error)?) -> Void struct NodeAccessConfiguration { var autoCreate: (() -> Bool)? @@ -97,7 +97,7 @@ class NodeAccess: NSObject { } } - private func updateHandle(_ node: MEGANode?, error: Error?, completion: NodeLoadCompletion?) { + private func updateHandle(_ node: MEGANode?, error: (any Error)?, completion: NodeLoadCompletion?) { self.handle = node?.handle if let node { self.nodePath = sdk.nodePath(for: node) diff --git a/iMEGA/Utils/NodeAccess/NodeLoadOperation.swift b/iMEGA/Utils/NodeAccess/NodeLoadOperation.swift index aecfcbda67..ea81f84f7b 100644 --- a/iMEGA/Utils/NodeAccess/NodeLoadOperation.swift +++ b/iMEGA/Utils/NodeAccess/NodeLoadOperation.swift @@ -21,7 +21,7 @@ final class NodeLoadOperation: MEGAOperation, NodeLoadOperationProtocol { // MARK: - Init init(autoCreate: (() -> Bool)?, - sdk: MEGASdk = MEGASdkManager.sharedMEGASdk(), + sdk: MEGASdk = .shared, loadNodeRequest: @escaping (any MEGARequestDelegate) -> Void, newNodeName: String? = nil, createNodeRequest: ((String, MEGANode, any MEGARequestDelegate) -> Void)? = nil, @@ -48,7 +48,7 @@ final class NodeLoadOperation: MEGAOperation, NodeLoadOperationProtocol { loadNodeFromRemote() } - func finishOperation(node: MEGANode?, error: Error?) { + func finishOperation(node: MEGANode?, error: (any Error)?) { completion(node, error) finish() } diff --git a/iMEGA/Utils/NodeAssetsManager.swift b/iMEGA/Utils/NodeAssetsManager.swift index e5068e1ac0..3c15fbaa8f 100644 --- a/iMEGA/Utils/NodeAssetsManager.swift +++ b/iMEGA/Utils/NodeAssetsManager.swift @@ -13,7 +13,7 @@ import MEGADomain } #if MAIN_APP_TARGET if CameraUploadNodeAccess.shared.isTargetNode(for: node) { - return Asset.Images.Filetypes.folderImage.image + return Asset.Images.Filetypes.folderCamera.image } else if BackupsUseCase(backupsRepository: BackupsRepository.newRepo, nodeRepository: NodeRepository.newRepo).isBackupDeviceFolder(node.toNodeEntity()) { return backupDeviceIcon(for: node) } diff --git a/iMEGA/Utils/OCWrapper/SaveNodeUseCaseOCWrapper.swift b/iMEGA/Utils/OCWrapper/SaveNodeUseCaseOCWrapper.swift index 7a0c37680c..cbdbbd58cb 100644 --- a/iMEGA/Utils/OCWrapper/SaveNodeUseCaseOCWrapper.swift +++ b/iMEGA/Utils/OCWrapper/SaveNodeUseCaseOCWrapper.swift @@ -1,16 +1,17 @@ import MEGADomain +import MEGAL10n import MEGARepo import MEGASDKRepo @objc class SaveNodeUseCaseOCWrapper: NSObject { let saveNodeUseCase = SaveNodeUseCase( - offlineFilesRepository: OfflineFilesRepository(store: MEGAStore.shareInstance(), sdk: MEGASdkManager.sharedMEGASdk()), + offlineFilesRepository: OfflineFilesRepository(store: MEGAStore.shareInstance(), sdk: MEGASdk.shared), fileCacheRepository: FileCacheRepository.newRepo, nodeRepository: NodeRepository.newRepo, photosLibraryRepository: PhotosLibraryRepository.newRepo, mediaUseCase: MediaUseCase(fileSearchRepo: FilesSearchRepository.newRepo), preferenceUseCase: PreferenceUseCase.default, - transferInventoryRepository: TransferInventoryRepository(sdk: MEGASdkManager.sharedMEGASdk())) + transferInventoryRepository: TransferInventoryRepository(sdk: MEGASdk.shared)) @objc func saveNodeIfNeeded(from transfer: MEGATransfer) { saveNodeUseCase.saveNode(from: transfer.toTransferEntity()) { result in diff --git a/iMEGA/Utils/Permissions/AlertModel+DevicePermissions.swift b/iMEGA/Utils/Permissions/AlertModel+DevicePermissions.swift index 366a2d7844..c73c567c05 100644 --- a/iMEGA/Utils/Permissions/AlertModel+DevicePermissions.swift +++ b/iMEGA/Utils/Permissions/AlertModel+DevicePermissions.swift @@ -1,6 +1,8 @@ +import MEGAL10n + extension AlertModel { static var audioMessage: String { - NSLocalizedString("microphonePermissions", + Strings.localized("microphonePermissions", comment: "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") } static func audio( @@ -8,25 +10,25 @@ extension AlertModel { completion: @escaping () -> Void ) -> AlertModel { model( - with: incomingCall ? NSLocalizedString("Incoming call", comment: "") : NSLocalizedString("attention", comment: "Alert title to attract attention"), + with: incomingCall ? Strings.localized("Incoming call", comment: "") : Strings.localized("attention", comment: "Alert title to attract attention"), message: audioMessage, completion: completion ) } static func photo(completion: @escaping () -> Void) -> AlertModel { model( - message: NSLocalizedString("photoLibraryPermissions", comment: "Alert message to explain that the MEGA app needs permission to access your device photos"), + message: Strings.localized("photoLibraryPermissions", comment: "Alert message to explain that the MEGA app needs permission to access your device photos"), completion: completion ) } static func video(completion: @escaping () -> Void) -> AlertModel { model( - message: NSLocalizedString("cameraPermissions", comment: "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"), + message: Strings.localized("cameraPermissions", comment: "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"), completion: completion ) } static func model( - with title: String = NSLocalizedString("attention", comment: "Alert title to attract attention"), + with title: String = Strings.localized("attention", comment: "Alert title to attract attention"), message: String, completion: @escaping () -> Void ) -> AlertModel { @@ -34,8 +36,8 @@ extension AlertModel { title: title, message: message, actions: [ - .init(title: NSLocalizedString("notNow", comment: ""), style: .cancel, handler: {}), - .init(title: NSLocalizedString("settingsTitle", comment: "Title of the Settings section"), style: .default, handler: { + .init(title: Strings.localized("notNow", comment: ""), style: .cancel, handler: {}), + .init(title: Strings.localized("settingsTitle", comment: "Title of the Settings section"), style: .default, handler: { completion() }) ] diff --git a/iMEGA/Utils/Permissions/CustomModalModel+DevicePermissions.swift b/iMEGA/Utils/Permissions/CustomModalModel+DevicePermissions.swift index fe47697b0a..558ef6a5aa 100644 --- a/iMEGA/Utils/Permissions/CustomModalModel+DevicePermissions.swift +++ b/iMEGA/Utils/Permissions/CustomModalModel+DevicePermissions.swift @@ -1,10 +1,12 @@ +import MEGAL10n + extension CustomModalModel { static func notifications(completion: @escaping (Dismisser) -> Void) -> Self { .init( image: UIImage(named: "notificationDevicePermission")!, - viewTitle: NSLocalizedString("Enable Notifications", comment: "Title label that explains that the user is going to be asked for the notifications permission"), - details: NSLocalizedString("We would like to send you notifications so you receive new messages on your device instantly.", comment: "Detailed explanation of why the user should give permission to deliver notifications"), - firstButtonTitle: NSLocalizedString("continue", comment: "'Next' button in a dialog"), + viewTitle: Strings.localized("Enable Notifications", comment: "Title label that explains that the user is going to be asked for the notifications permission"), + details: Strings.localized("We would like to send you notifications so you receive new messages on your device instantly.", comment: "Detailed explanation of why the user should give permission to deliver notifications"), + firstButtonTitle: Strings.localized("continue", comment: "'Next' button in a dialog"), dismissButtonTitle: nil, firstCompletion: completion ) @@ -16,10 +18,10 @@ extension CustomModalModel { ) -> Self { .init( image: UIImage(named: "groupChat")!, - viewTitle: incomingCall ? NSLocalizedString("Incoming call", comment: "") : NSLocalizedString("Enable Microphone and Camera", comment: "Title label that explains that the user is going to be asked for the microphone and camera permission"), - details: NSLocalizedString("To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone", comment: "Detailed explanation of why the user should give permission to access to the camera and the microphone"), - firstButtonTitle: NSLocalizedString("Allow Access", comment: "Button which triggers a request for a specific permission, that have been explained to the user beforehand"), - dismissButtonTitle: NSLocalizedString("notNow", comment: ""), + viewTitle: incomingCall ? Strings.localized("Incoming call", comment: "") : Strings.localized("Enable Microphone and Camera", comment: "Title label that explains that the user is going to be asked for the microphone and camera permission"), + details: Strings.localized("To make encrypted voice and video calls, allow MEGA access to your Camera and Microphone", comment: "Detailed explanation of why the user should give permission to access to the camera and the microphone"), + firstButtonTitle: Strings.localized("Allow Access", comment: "Button which triggers a request for a specific permission, that have been explained to the user beforehand"), + dismissButtonTitle: Strings.localized("notNow", comment: ""), firstCompletion: completion ) } diff --git a/iMEGA/Utils/Shared Views/Cells/GenericHeaderFooterView.swift b/iMEGA/Utils/Shared Views/Cells/GenericHeaderFooterView.swift index 183b1e8d1c..755ca2fbee 100644 --- a/iMEGA/Utils/Shared Views/Cells/GenericHeaderFooterView.swift +++ b/iMEGA/Utils/Shared Views/Cells/GenericHeaderFooterView.swift @@ -1,4 +1,3 @@ - import UIKit final class GenericHeaderFooterView: UITableViewHeaderFooterView { diff --git a/iMEGA/Utils/Shared Views/Cells/Node/NodeCellViewModel.swift b/iMEGA/Utils/Shared Views/Cells/Node/NodeCellViewModel.swift index 719b66e27b..29b86350cd 100644 --- a/iMEGA/Utils/Shared Views/Cells/Node/NodeCellViewModel.swift +++ b/iMEGA/Utils/Shared Views/Cells/Node/NodeCellViewModel.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation enum NodeCellAction: ActionType { @@ -124,7 +125,7 @@ final class NodeCellViewModel: ViewModelType { private func iconForNode() { if nodeModel.isFolder { if nodeModel.name == Strings.Localizable.cameraUploadsLabel { - let cameraUploadsFolderImageName = "folder_image" + let cameraUploadsFolderImageName = "folder_camera" self.invokeCommand?(.setIcon(cameraUploadsFolderImageName)) } else if nodeModel.name == Strings.Localizable.myChatFiles { accountUseCase.getMyChatFilesFolder { [weak self] in diff --git a/iMEGA/Utils/Shared Views/DynamicType/DynamicTypeComponentProtocol.swift b/iMEGA/Utils/Shared Views/DynamicType/DynamicTypeComponentProtocol.swift index 26188348c5..148e7448a1 100644 --- a/iMEGA/Utils/Shared Views/DynamicType/DynamicTypeComponentProtocol.swift +++ b/iMEGA/Utils/Shared Views/DynamicType/DynamicTypeComponentProtocol.swift @@ -1,4 +1,3 @@ - protocol DynamicTypeComponentProtocol where Self: UIView { func observeContentSizeUpdates() func removeObserver() diff --git a/iMEGA/Utils/Shared Views/Shared Controls/MEGAAVViewController.h b/iMEGA/Utils/Shared Views/Shared Controls/MEGAAVViewController.h index 8b3777418f..a37995acee 100644 --- a/iMEGA/Utils/Shared Views/Shared Controls/MEGAAVViewController.h +++ b/iMEGA/Utils/Shared Views/Shared Controls/MEGAAVViewController.h @@ -1,4 +1,3 @@ - #import #import #import diff --git a/iMEGA/Utils/Shared Views/Shared Controls/MEGAAVViewController.m b/iMEGA/Utils/Shared Views/Shared Controls/MEGAAVViewController.m index 53b0657cf1..67e56f582b 100644 --- a/iMEGA/Utils/Shared Views/Shared Controls/MEGAAVViewController.m +++ b/iMEGA/Utils/Shared Views/Shared Controls/MEGAAVViewController.m @@ -1,4 +1,3 @@ - #import "MEGAAVViewController.h" #import "LTHPasscodeViewController.h" @@ -12,6 +11,8 @@ #import "MEGAStore.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + static const NSUInteger MIN_SECOND = 10; // Save only where the users were playing the file, if the streaming second is greater than this value. @interface MEGAAVViewController () @@ -86,14 +87,14 @@ - (void)viewDidAppear:(BOOL)animated { MOMediaDestination *mediaDestination = [[MEGAStore shareInstance] fetchMediaDestinationWithFingerprint:fingerprint]; if (mediaDestination.destination.longLongValue > 0 && mediaDestination.timescale.intValue > 0) { if ([FileExtensionGroupOCWrapper verifyIsVideo:[self fileName]]) { - NSString *infoVideoDestination = NSLocalizedString(@"video.alert.resumeVideo.message", @"Message to show the user info (video name and time) about the resume of the video"); + NSString *infoVideoDestination = LocalizedString(@"video.alert.resumeVideo.message", @"Message to show the user info (video name and time) about the resume of the video"); infoVideoDestination = [infoVideoDestination stringByReplacingOccurrencesOfString:@"%1$s" withString:[self fileName]]; infoVideoDestination = [infoVideoDestination stringByReplacingOccurrencesOfString:@"%2$s" withString:[self timeForMediaDestination:mediaDestination]]; - UIAlertController *resumeOrRestartAlert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"video.alert.resumeVideo.title", @"Alert title shown for video with options to resume playing the video or start from the beginning") message:infoVideoDestination preferredStyle:UIAlertControllerStyleAlert]; - [resumeOrRestartAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"video.alert.resumeVideo.button.resume", @"Alert button title that will resume playing the video") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + UIAlertController *resumeOrRestartAlert = [UIAlertController alertControllerWithTitle:LocalizedString(@"video.alert.resumeVideo.title", @"Alert title shown for video with options to resume playing the video or start from the beginning") message:infoVideoDestination preferredStyle:UIAlertControllerStyleAlert]; + [resumeOrRestartAlert addAction:[UIAlertAction actionWithTitle:LocalizedString(@"video.alert.resumeVideo.button.resume", @"Alert button title that will resume playing the video") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self seekToDestination:mediaDestination play:YES]; }]]; - [resumeOrRestartAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"video.alert.resumeVideo.button.restart", @"Alert button title that will start playing the video from the beginning") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [resumeOrRestartAlert addAction:[UIAlertAction actionWithTitle:LocalizedString(@"video.alert.resumeVideo.button.restart", @"Alert button title that will start playing the video from the beginning") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [self seekToDestination:nil play:YES]; }]]; [self presentViewController:resumeOrRestartAlert animated:YES completion:nil]; @@ -136,7 +137,7 @@ - (void)viewDidDisappear:(BOOL)animated { [[LTHPasscodeViewController sharedUser] showLockScreenOver:UIApplication.mnz_presentingViewController.view withAnimation:YES withLogout:YES - andLogoutTitle:NSLocalizedString(@"logoutLabel", nil)]; + andLogoutTitle:LocalizedString(@"logoutLabel", @"")]; } [self deallocPlayer]; diff --git a/iMEGA/Utils/Shared Views/Shared Controls/MEGAImagePickerController.h b/iMEGA/Utils/Shared Views/Shared Controls/MEGAImagePickerController.h index 69c0908c3f..2d62920dc2 100644 --- a/iMEGA/Utils/Shared Views/Shared Controls/MEGAImagePickerController.h +++ b/iMEGA/Utils/Shared Views/Shared Controls/MEGAImagePickerController.h @@ -1,4 +1,3 @@ - @interface MEGAImagePickerController : UIImagePickerController - (id)init NS_UNAVAILABLE; diff --git a/iMEGA/Utils/Shared Views/Shared Controls/MEGAImagePickerController.m b/iMEGA/Utils/Shared Views/Shared Controls/MEGAImagePickerController.m index 38478b5331..5e0a0d175d 100644 --- a/iMEGA/Utils/Shared Views/Shared Controls/MEGAImagePickerController.m +++ b/iMEGA/Utils/Shared Views/Shared Controls/MEGAImagePickerController.m @@ -1,4 +1,3 @@ - #import "MEGAImagePickerController.h" #import @@ -15,6 +14,8 @@ #import "NSString+MNZCategory.h" #import "MEGA-Swift.h" + +@import MEGAL10nObjc; @import MEGASDKRepo; @interface MEGAImagePickerController () @@ -78,7 +79,7 @@ - (void)viewDidLoad { if (![UIImagePickerController isSourceTypeAvailable:self.sourceType]) { if (self.sourceType == UIImagePickerControllerSourceTypeCamera) { - [SVProgressHUD showImage:[UIImage imageNamed:@"hudNoCamera"] status:NSLocalizedString(@"noCamera", @"Error message shown when there's no camera available on the device")]; + [SVProgressHUD showImage:[UIImage imageNamed:@"hudNoCamera"] status:LocalizedString(@"noCamera", @"Error message shown when there's no camera available on the device")]; } return; } diff --git a/iMEGA/Utils/Shared Views/Snackbar/SnackBarPresenting.swift b/iMEGA/Utils/Shared Views/Snackbar/SnackBarPresenting.swift index 1c4adbc459..c7ad86f1ce 100644 --- a/iMEGA/Utils/Shared Views/Snackbar/SnackBarPresenting.swift +++ b/iMEGA/Utils/Shared Views/Snackbar/SnackBarPresenting.swift @@ -1,4 +1,3 @@ - protocol SnackBarPresenting where Self: UIViewController { func snackBarContainerView() -> UIView? func layout(snackBarView: UIView?) @@ -13,14 +12,21 @@ extension SnackBarPresenting { showSnackBar() } - func dismissSnackBar() { + func dismissSnackBar(immediate: Bool = false) { guard let containerView = snackBarContainerView() else { return } + let completion = { [weak self] in + self?.layout(snackBarView: nil) + } + + guard !immediate else { + completion() + return + } + UIView.animate(withDuration: 0.5, animations: { containerView.alpha = 0.0 - }, completion: { _ in - self.layout(snackBarView: nil) - }) + }, completion: { _ in completion() }) } @MainActor diff --git a/iMEGA/Utils/Shared Views/Snackbar/SnackBarRouter.swift b/iMEGA/Utils/Shared Views/Snackbar/SnackBarRouter.swift index 0eaf48d7d3..d79a70db59 100644 --- a/iMEGA/Utils/Shared Views/Snackbar/SnackBarRouter.swift +++ b/iMEGA/Utils/Shared Views/Snackbar/SnackBarRouter.swift @@ -21,7 +21,7 @@ final class SnackBarRouter: NSObject { presenter.presentSnackBar(viewController: viewController) } - func dismissSnackBar() { - presenter?.dismissSnackBar() + func dismissSnackBar(immediate: Bool = false) { + presenter?.dismissSnackBar(immediate: immediate) } } diff --git a/iMEGA/Utils/Shared Views/Snackbar/SnackBarViewModel.swift b/iMEGA/Utils/Shared Views/Snackbar/SnackBarViewModel.swift index 870193da90..df60285226 100644 --- a/iMEGA/Utils/Shared Views/Snackbar/SnackBarViewModel.swift +++ b/iMEGA/Utils/Shared Views/Snackbar/SnackBarViewModel.swift @@ -1,7 +1,8 @@ import Combine final class SnackBarViewModel: ObservableObject { - @Published var snackBar: SnackBar + + @Published private(set) var snackBar: SnackBar @Published var isShowSnackBar: Bool = true var displayDuration: Double = 4.0 @@ -14,6 +15,11 @@ final class SnackBarViewModel: ObservableObject { configureSnackBar() } + func update(snackBar: SnackBar) { + self.snackBar = snackBar + configureSnackBar() + } + private func configureSnackBar() { UIImpactFeedbackGenerator(style: .light).impactOccurred() diff --git a/iMEGA/Utils/Shared Views/Theme/Style/StyleFactory/Layer3/ExploreViewStyleFactory.swift b/iMEGA/Utils/Shared Views/Theme/Style/StyleFactory/Layer3/ExploreViewStyleFactory.swift index 2af0b2c430..b3ef94e70a 100644 --- a/iMEGA/Utils/Shared Views/Theme/Style/StyleFactory/Layer3/ExploreViewStyleFactory.swift +++ b/iMEGA/Utils/Shared Views/Theme/Style/StyleFactory/Layer3/ExploreViewStyleFactory.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n enum MEGAExploreViewStyle: Int { case favourites diff --git a/iMEGA/Utils/Shared Views/UIKit+Tools/TableView/Configurations/TableViewEmptyStateConfiguration.swift b/iMEGA/Utils/Shared Views/UIKit+Tools/TableView/Configurations/TableViewEmptyStateConfiguration.swift index 1b16e921ee..b896f8ccd0 100644 --- a/iMEGA/Utils/Shared Views/UIKit+Tools/TableView/Configurations/TableViewEmptyStateConfiguration.swift +++ b/iMEGA/Utils/Shared Views/UIKit+Tools/TableView/Configurations/TableViewEmptyStateConfiguration.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n struct EmptyStateConfiguration { diff --git a/iMEGA/Utils/Shared Views/UIKit+Tools/TableView/TableViewProxy.swift b/iMEGA/Utils/Shared Views/UIKit+Tools/TableView/TableViewProxy.swift index 7541675c8a..24ca8bee19 100644 --- a/iMEGA/Utils/Shared Views/UIKit+Tools/TableView/TableViewProxy.swift +++ b/iMEGA/Utils/Shared Views/UIKit+Tools/TableView/TableViewProxy.swift @@ -14,7 +14,7 @@ final class TableViewProxy: private var emptyStateConfiguration: EmptyStateConfiguration? private var dataSourceConfiguration: TableDataSourceConfiguration? - + var selectionAction: ((CellItem) -> Void)? var configureCell: ((UITableViewCell, CellItem) -> Void) diff --git a/iMEGA/Utils/Shared Views/ViewControllers/AccountExpiredViewController/AccountExpiredViewController.swift b/iMEGA/Utils/Shared Views/ViewControllers/AccountExpiredViewController/AccountExpiredViewController.swift index e5ce9e0139..376900d760 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/AccountExpiredViewController/AccountExpiredViewController.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/AccountExpiredViewController/AccountExpiredViewController.swift @@ -1,4 +1,4 @@ - +import MEGAL10n import UIKit class AccountExpiredViewController: UIViewController { @@ -22,13 +22,13 @@ class AccountExpiredViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - MEGASdkManager.sharedMEGASdk().add(self) + MEGASdk.shared.add(self) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - MEGASdkManager.sharedMEGASdk().remove(self) + MEGASdk.shared.remove(self) } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { @@ -42,11 +42,11 @@ class AccountExpiredViewController: UIViewController { // MARK: - Set contents func getAccountDetails() { activityIndicator.startAnimating() - MEGASdkManager.sharedMEGASdk().getAccountDetails() + MEGASdk.shared.getAccountDetails() } func configContent() { - guard let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails else { return } + guard let accountDetails = MEGASdk.shared.mnz_accountDetails else { return } switch accountDetails.type { case .proFlexi: configProFlexiAccount() @@ -67,7 +67,9 @@ class AccountExpiredViewController: UIViewController { func configBusinessAccount() { titleLabel.text = Strings.Localizable.yourBusinessAccountIsExpired dismissButton.setTitle(Strings.Localizable.dismiss, for: .normal) - if MEGASdkManager.sharedMEGASdk().isMasterBusinessAccount { + titleLabel.text = Strings.Localizable.yourBusinessAccountIsExpired + dismissButton.setTitle(Strings.Localizable.dismiss, for: .normal) + if MEGASdk.shared.isMasterBusinessAccount { imageView.image = Asset.Images.Business.accountExpiredAdmin.image detailLabel.text = Strings.Localizable.ThereHasBeenAProblemProcessingYourPayment.megaIsLimitedToViewOnlyUntilThisIssueHasBeenFixedInADesktopWebBrowser } else { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/ActionSheetViewControllerFactory/ActionSheetFactory.swift b/iMEGA/Utils/Shared Views/ViewControllers/ActionSheetViewControllerFactory/ActionSheetFactory.swift index 6fd4b7cec6..1296097eaa 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/ActionSheetViewControllerFactory/ActionSheetFactory.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/ActionSheetViewControllerFactory/ActionSheetFactory.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n protocol ActionSheetFactoryProtocol { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerContainerViewController.swift b/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerContainerViewController.swift index d7d818ad18..a75a758f89 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerContainerViewController.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerContainerViewController.swift @@ -1,4 +1,3 @@ - final class BannerContainerViewController: UIViewController { @IBOutlet weak var bannerContainerView: UIView! @IBOutlet weak var contentContainerView: UIView! diff --git a/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerContainerViewRouter.swift b/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerContainerViewRouter.swift index 19df6babf4..11ca5f7e67 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerContainerViewRouter.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerContainerViewRouter.swift @@ -1,4 +1,3 @@ - final class BannerContainerViewRouter: NSObject { private weak var navigationController: UINavigationController? private var contentViewController: UIViewController diff --git a/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerType.swift b/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerType.swift index b13f365ebb..dd8f4458c0 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerType.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/Banner Container/Scenes/BannerContainerScene/BannerType.swift @@ -1,4 +1,3 @@ - @objc enum BannerType: Int { case warning = 0 diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/ActionSheetActions.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/ActionSheetActions.swift index 345c6a38a3..05a8869d47 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/ActionSheetActions.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/ActionSheetActions.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import UIKit class BaseAction: NSObject { @@ -244,7 +245,7 @@ extension NodeAction { class func labelAction(label: MEGANodeLabel) -> NodeAction { let labelString = MEGANode.string(for: label) - let detailText = NSLocalizedString(labelString!, comment: "") + let detailText = Strings.localized(labelString!, comment: "") let image = UIImage(named: labelString!) return NodeAction(title: Strings.Localizable.CloudDrive.Sort.label, detail: (label != .unknown ? detailText : nil), accessoryView: (label != .unknown ? UIImageView(image: image) : UIImageView(image: Asset.Images.Generic.standardDisclosureIndicator.image)), image: Asset.Images.NodeActions.label.image, type: .label) diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/NodeActionViewController.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/NodeActionViewController.swift index 616363a552..950fad39e3 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/NodeActionViewController.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/NodeActionViewController.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASDKRepo import MEGASwift import UIKit @@ -37,7 +38,7 @@ class NodeActionViewController: ActionSheetViewController { isIncoming: Bool = false, isBackupNode: Bool, sender: Any) { - guard let node = MEGASdkManager.sharedMEGASdk().node(forHandle: node) else { return nil } + guard let node = MEGASdk.shared.node(forHandle: node) else { return nil } self.init(node: node, delegate: delegate, displayMode: displayMode, isIncoming: isIncoming, isBackupNode: isBackupNode, sender: sender) } @@ -48,7 +49,7 @@ class NodeActionViewController: ActionSheetViewController { isBackupNode: Bool = false, sender: Any) { - guard let node = MEGASdkManager.sharedMEGASdk().node(forHandle: nodeHandle) else { return nil } + guard let node = MEGASdk.shared.node(forHandle: nodeHandle) else { return nil } self.nodes = [node] self.displayMode = displayMode self.delegate = delegate @@ -60,7 +61,7 @@ class NodeActionViewController: ActionSheetViewController { self.actions = NodeActionBuilder() .setDisplayMode(displayMode) - .setAccessLevel(MEGASdkManager.sharedMEGASdk().accessLevel(for: node)) + .setAccessLevel(MEGASdk.shared.accessLevel(for: node)) .setIsBackupNode(isBackupNode) .build() } @@ -184,10 +185,10 @@ class NodeActionViewController: ActionSheetViewController { .setDisplayMode(self.displayMode) .setIsPdf(node.name?.pathExtension == "pdf") .setIsLink(isLink) - .setAccessLevel(MEGASdkManager.sharedMEGASdk().accessLevel(for: node)) + .setAccessLevel(MEGASdk.shared.accessLevel(for: node)) .setIsRestorable(isBackupNode ? false : node.mnz_isRestorable()) .setVersionCount(node.mnz_numberOfVersions() - 1) - .setIsChildVersion(MEGASdkManager.sharedMEGASdk().node(forHandle: node.parentHandle)?.isFile()) + .setIsChildVersion(MEGASdk.shared.node(forHandle: node.parentHandle)?.isFile()) .setIsInVersionsView(isInVersionsView) .setIsBackupNode(isBackupNode) .setIsExported(node.isExported()) @@ -285,12 +286,11 @@ class NodeActionViewController: ActionSheetViewController { subtitleLabel.autoAlignAxis(.horizontal, toSameAxisOf: headerView!, withOffset: 10) subtitleLabel.font = .preferredFont(forTextStyle: .caption1) subtitleLabel.adjustsFontForContentSizeCategory = true - - let sharedMEGASdk = displayMode == .folderLink || displayMode == .nodeInsideFolderLink ? MEGASdkManager.sharedMEGASdkFolder() : MEGASdkManager.sharedMEGASdk() + if node.isFile() { - subtitleLabel.text = Helper.sizeAndModicationDate(for: node, api: sharedMEGASdk) + subtitleLabel.text = sizeAndModicationDate(node.toNodeEntity()) } else { - subtitleLabel.text = Helper.filesAndFolders(inFolderNode: node, api: sharedMEGASdk) + subtitleLabel.text = getFilesAndFolders(node.toNodeEntity()) } headerView?.addSubview(separatorLineView) @@ -301,6 +301,26 @@ class NodeActionViewController: ActionSheetViewController { separatorLineView.backgroundColor = tableView.separatorColor } + private func sizeAndModicationDate(_ nodeModel: NodeEntity) -> String { + let modificationTime = nodeModel.modificationTime as NSDate + let modificationTimeString: String = modificationTime.mnz_formattedDateMediumTimeShortStyle() + + return sizeForFile(nodeModel) + " • " + modificationTimeString + } + + private func sizeForFile(_ nodeModel: NodeEntity) -> String { + return String.memoryStyleString(fromByteCount: Int64(nodeModel.size)) + } + + private func getFilesAndFolders(_ nodeModel: NodeEntity) -> String { + let nodeUseCase = NodeUseCase(nodeDataRepository: NodeDataRepository.newRepo, nodeValidationRepository: NodeValidationRepository.newRepo) + let numberOfFilesAndFolders = nodeUseCase.getFilesAndFolders(nodeHandle: nodeModel.handle) + let numberOfFiles = numberOfFilesAndFolders.0 + let numberOfFolders = numberOfFilesAndFolders.1 + let numberOfFilesAndFoldersString = NSString.mnz_string(byFiles: numberOfFiles, andFolders: numberOfFolders) + return numberOfFilesAndFoldersString + } + private func setupActions(node: MEGANode, displayMode: DisplayMode, isIncoming: Bool = false, isInVersionsView: Bool = false, isBackupNode: Bool, sharedFolder: MEGAShare = MEGAShare(), shouldShowVerifyContact: Bool = false) { let isImageOrVideoFile = node.name?.fileExtensionGroup.isVisualMedia == true let isMediaFile = node.isFile() && isImageOrVideoFile && node.mnz_isPlayable() @@ -313,7 +333,7 @@ class NodeActionViewController: ActionSheetViewController { self.actions = NodeActionBuilder() .setDisplayMode(displayMode) - .setAccessLevel(MEGASdkManager.sharedMEGASdk().accessLevel(for: node)) + .setAccessLevel(MEGASdk.shared.accessLevel(for: node)) .setIsMediaFile(isMediaFile) .setIsEditableTextFile(isEditableTextFile) .setIsFile(node.isFile()) @@ -326,7 +346,7 @@ class NodeActionViewController: ActionSheetViewController { .setisIncomingShareChildView(isIncoming) .setIsExported(node.isExported()) .setIsOutshare(node.isOutShare()) - .setIsChildVersion(MEGASdkManager.sharedMEGASdk().node(forHandle: node.parentHandle)?.isFile()) + .setIsChildVersion(MEGASdk.shared.node(forHandle: node.parentHandle)?.isFile()) .setIsInVersionsView(isInVersionsView) .setIsTakedown(isTakedown) .setIsVerifyContact(isVerifyContact, diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/TransferActionViewController.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/TransferActionViewController.swift index ed2db189b5..3a5dcf28e7 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/TransferActionViewController.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomActionViewController/TransferActionViewController.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import UIKit @objc protocol TransferActionViewControllerDelegate: NodeActionViewControllerDelegate { @@ -31,7 +32,7 @@ class TransferActionViewController: NodeActionViewController { guard let error = transfer.lastErrorExtended, let errorString = MEGAError.errorStringWithErrorCode(error.type.rawValue, context: .upload) else { return } - subtitleLabel.text = "\(transferFailed) \(NSLocalizedString(errorString, comment: ""))" + subtitleLabel.text = "\(transferFailed) \(Strings.localized(errorString, comment: ""))" default: break diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/CustomModalAlertViewController.h b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/CustomModalAlertViewController.h index 9dad3e141f..7d8ac787f4 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/CustomModalAlertViewController.h +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/CustomModalAlertViewController.h @@ -1,4 +1,3 @@ - #import @class CountdownTimer; diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/CustomModalAlertViewController.m b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/CustomModalAlertViewController.m index 7b0fd99205..8f7fd74832 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/CustomModalAlertViewController.m +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/CustomModalAlertViewController.m @@ -1,4 +1,3 @@ - #import "CustomModalAlertViewController.h" #import "AchievementsViewController.h" diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Business.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Business.swift index f7d090a9fb..ecf9d04695 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Business.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Business.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n extension CustomModalAlertViewController { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Config.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Config.swift index 70dd70fed9..3b5aa5a35c 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Config.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Config.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n extension CustomModalAlertViewController { func configureUpgradeAccountThreeButtons(_ titleText: String, _ detailText: String, _ monospaceText: String?, _ imageName: String?, hasBonusButton: Bool = true, firstButtonTitle: String = Strings.Localizable.seePlans, dismissTitle: String = Strings.Localizable.dismiss) { @@ -15,7 +16,7 @@ extension CustomModalAlertViewController { } self.firstButtonTitle = firstButtonTitle - if MEGASdkManager.sharedMEGASdk().isAchievementsEnabled && hasBonusButton { + if MEGASdk.shared.isAchievementsEnabled && hasBonusButton { secondButtonTitle = Strings.Localizable.General.Button.getBonus } dismissButtonTitle = dismissTitle diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Contacts.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Contacts.swift index 76dc5dc93b..2a64543515 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Contacts.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Contacts.swift @@ -1,5 +1,5 @@ - import Foundation +import MEGAL10n extension CustomModalAlertViewController { @@ -36,7 +36,7 @@ extension CustomModalAlertViewController { firstCompletion = { [weak self] in let inviteContactRequestDelegate = MEGAInviteContactRequestDelegate(numberOfRequests: 1) - MEGASdkManager.sharedMEGASdk().inviteContact(withEmail: email, message: "", action: .add, delegate: inviteContactRequestDelegate) + MEGASdk.shared.inviteContact(withEmail: email, message: "", action: .add, delegate: inviteContactRequestDelegate) self?.dismiss(animated: true, completion: nil) } diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+CookieDialog.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+CookieDialog.swift index b3feab8667..5ac6df6073 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+CookieDialog.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+CookieDialog.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n import MEGASDKRepo extension CustomModalAlertViewController { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+LaunchTab.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+LaunchTab.swift index 4a76d32f52..a851b04175 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+LaunchTab.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+LaunchTab.swift @@ -1,3 +1,5 @@ +import MEGAL10n + extension CustomModalAlertViewController { func configureForChangeLaunchTab() { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+SharedItem.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+SharedItem.swift index 0cecfb0eae..548a94879a 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+SharedItem.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+SharedItem.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n extension CustomModalAlertViewController { func configureForPendingUnverifiedOutshare(for email: String) { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Storage.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Storage.swift index d8e333c616..5d06eb4d89 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Storage.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Storage.swift @@ -1,10 +1,11 @@ import Foundation import MEGADomain +import MEGAL10n import MEGASDKRepo extension CustomModalAlertViewController { func configureForStorageEvent(_ event: MEGAEvent) { - let imageName = event.number == StorageState.orange.rawValue ? Asset.Images.WarningStorageAlmostFull.storageAlmostFull.name : Asset.Images.WarningStorageAlmostFull.storageFull.name + let imageName = event.number == StorageState.orange.rawValue ? Asset.Images.WarningStorageAlmostFull.storageAlmostFull.name : Asset.Images.WarningStorageAlmostFull.warningStorageFull.name let title = event.number == StorageState.orange.rawValue ? Strings.Localizable.upgradeAccount : Strings.Localizable.Dialog.Storage.Odq.title @@ -13,7 +14,7 @@ extension CustomModalAlertViewController { if MEGAPurchase.sharedInstance().pricing == nil { SVProgressHUD.show() - let sdkDelegate = MEGAResultRequestDelegate { (result) in + let sdkDelegate = RequestDelegate { result in SVProgressHUD.dismiss() switch result { case .success(let request): @@ -24,7 +25,7 @@ extension CustomModalAlertViewController { return } } - MEGASdkManager.sharedMEGASdk().getPricingWith(sdkDelegate) + MEGASdk.shared.getPricingWith(sdkDelegate) } } @@ -45,8 +46,8 @@ extension CustomModalAlertViewController { func configureForStorageQuotaError(_ uploading: Bool) { var imageName: String? - if let accountDetails = MEGASdkManager.sharedMEGASdk().mnz_accountDetails { - imageName = accountDetails.storageMax.int64Value > accountDetails.storageUsed.int64Value ? Asset.Images.WarningStorageAlmostFull.storageAlmostFull.name : Asset.Images.WarningStorageAlmostFull.storageFull.name + if let accountDetails = MEGASdk.shared.mnz_accountDetails { + imageName = accountDetails.storageMax.int64Value > accountDetails.storageUsed.int64Value ? Asset.Images.WarningStorageAlmostFull.storageAlmostFull.name : Asset.Images.WarningStorageAlmostFull.warningStorageFull.name } let title = Strings.Localizable.upgradeAccount @@ -65,7 +66,7 @@ extension CustomModalAlertViewController { case .albumLink: title = Strings.Localizable.AlbumLink.ImportFailed.StorageQuotaWillExceed.Alert.title detailText = Strings.Localizable.AlbumLink.ImportFailed.StorageQuotaWillExceed.Alert.detail - imageName = Asset.Images.WarningStorageAlmostFull.storageFull + imageName = Asset.Images.WarningStorageAlmostFull.warningStorageFull } configureUpgradeAccountThreeButtons( diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Transfer.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Transfer.swift index d6f1a8ad7f..9581da1bde 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Transfer.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+Transfer.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGASwift extension CustomModalAlertViewController { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+TwoFactorAuthentication.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+TwoFactorAuthentication.swift index bfe83ec119..1cb97a7f03 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+TwoFactorAuthentication.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+TwoFactorAuthentication.swift @@ -1,4 +1,6 @@ import Foundation +import MEGAL10n +import MEGASDKRepo extension CustomModalAlertViewController { @objc func configureForTwoFactorAuthentication(requestedByUser: Bool) { @@ -16,9 +18,11 @@ extension CustomModalAlertViewController { firstCompletion = { [weak self] in self?.dismiss(animated: true) { SVProgressHUD.show() - MEGASdkManager.sharedMEGASdk().multiFactorAuthGetCode(with: MEGAGenericRequestDelegate.init(completion: { (request, error) in - if error.type != .apiOk { - SVProgressHUD.showError(withStatus: NSLocalizedString(error.name, comment: "")) + MEGASdk.shared.multiFactorAuthGetCode(with: RequestDelegate { result in + guard case let .success(request) = result else { + if case let .failure(error) = result { + SVProgressHUD.showError(withStatus: Strings.localized(error.name, comment: "")) + } return } @@ -28,7 +32,7 @@ extension CustomModalAlertViewController { enablingTwoFactorAuthenticationVC.hidesBottomBarWhenPushed = true UIApplication.mnz_visibleViewController().navigationController?.pushViewController(enablingTwoFactorAuthenticationVC, animated: true) - })) + }) } } diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+UpgradeSecurity.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+UpgradeSecurity.swift index 0550f59d8c..3cff3c922f 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+UpgradeSecurity.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+UpgradeSecurity.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n extension CustomModalAlertViewController { func configureForUpgradeSecurity() { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+UpgradeToPro.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+UpgradeToPro.swift index e05934aee1..afe91324ef 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+UpgradeToPro.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewController+UpgradeToPro.swift @@ -1,4 +1,5 @@ import Foundation +import MEGAL10n extension CustomModalAlertViewController { func configureUpgradeToPro(onConfirm: @escaping () -> Void, onCancel: @escaping () -> Void) { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewcontroller+EnableKeyRotation.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewcontroller+EnableKeyRotation.swift index 0b1ea5d4cc..220bb3a5ba 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewcontroller+EnableKeyRotation.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Extensions/CustomModalAlertViewcontroller+EnableKeyRotation.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n extension CustomModalAlertViewController { func configureForEnableKeyRotation(in chatId: ChatIdEntity) { @@ -10,7 +11,7 @@ extension CustomModalAlertViewController { firstCompletion = { [weak self] in self?.dismiss(animated: true, completion: { - MEGASdkManager.sharedMEGAChatSdk().setPublicChatToPrivate(chatId) + MEGAChatSdk.shared.setPublicChatToPrivate(chatId) }) } } diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertContactsRouter.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertContactsRouter.swift index 250f46a438..e905cbb21b 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertContactsRouter.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertContactsRouter.swift @@ -1,4 +1,3 @@ - import Foundation @objc final class CustomModalAlertContactsRouter: CustomModalAlertRouter { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertRouter.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertRouter.swift index 3f2110c2f8..ff6866cc26 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertRouter.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertRouter.swift @@ -1,4 +1,3 @@ - import Foundation import MEGADomain import MEGAPresentation diff --git a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertStorageRouter.swift b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertStorageRouter.swift index 65d0d9f52a..eca653f86e 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertStorageRouter.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/CustomModalAlert/Routers/CustomModalAlertStorageRouter.swift @@ -1,4 +1,3 @@ - import Foundation @objc final class CustomModalAlertStorageRouter: CustomModalAlertRouter { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/PreviewDocumentViewController.m b/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/PreviewDocumentViewController.m index 4cb93db994..d60cbdf1b7 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/PreviewDocumentViewController.m +++ b/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/PreviewDocumentViewController.m @@ -1,4 +1,3 @@ - #import "PreviewDocumentViewController.h" #import @@ -23,6 +22,8 @@ #import "MEGAStore.h" #import "MEGA-Swift.h" #import "UIView+MNZCategory.h" + +@import MEGAL10nObjc; @import PureLayout; @interface PreviewDocumentViewController () { @@ -71,12 +72,12 @@ - (void)viewDidLoad { [self configureNavigation]; [self updateAppearance]; - self.closeBarButtonItem.title = NSLocalizedString(@"close", @"A button label."); + self.closeBarButtonItem.title = LocalizedString(@"close", @"A button label."); - self.moreBarButtonItem.accessibilityLabel = NSLocalizedString(@"more", @"Top menu option which opens more menu options in a context menu."); + self.moreBarButtonItem.accessibilityLabel = LocalizedString(@"more", @"Top menu option which opens more menu options in a context menu."); if (self.showUnknownEncodeHud) { - [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"general.textEditor.hud.unknownEncode", @"Hud info message when read unknown encode file.")]; + [SVProgressHUD showErrorWithStatus:LocalizedString(@"general.textEditor.hud.unknownEncode", @"Hud info message when read unknown encode file.")]; } } @@ -263,7 +264,7 @@ - (void)updateAppearance { - (void)createOpenZipButton { UIButton *openZipButton = [UIButton newAutoLayoutView]; - [openZipButton setTitle:NSLocalizedString(@"openButton", @"Button title to trigger the action of opening the file without downloading or opening it.") forState:UIControlStateNormal]; + [openZipButton setTitle:LocalizedString(@"openButton", @"Button title to trigger the action of opening the file without downloading or opening it.") forState:UIControlStateNormal]; [openZipButton mnz_setupBasic:self.traitCollection]; [self.view addSubview:openZipButton]; [openZipButton autoSetDimension:ALDimensionWidth toSize:300]; @@ -425,16 +426,7 @@ - (void)nodeAction:(NodeActionViewController *)nodeAction didSelect:(MegaNodeAct } case MegaNodeActionTypeFavourite: { - MEGAGenericRequestDelegate *delegate = [MEGAGenericRequestDelegate.alloc initWithCompletion:^(MEGARequest * _Nonnull request, MEGAError * _Nonnull error) { - if (error.type == MEGAErrorTypeApiOk) { - if (request.numDetails == 1) { - [[QuickAccessWidgetManager.alloc init] insertFavouriteItemFor:node]; - } else { - [[QuickAccessWidgetManager.alloc init] deleteFavouriteItemFor:node]; - } - } - }]; - [MEGASdkManager.sharedMEGASdk setNodeFavourite:node favourite:!node.isFavourite delegate:delegate]; + [MEGASdk.shared setNodeFavourite:node favourite:!node.isFavourite]; break; } diff --git a/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/SearchInPdfViewController.h b/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/SearchInPdfViewController.h index b87a296258..c63d489f34 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/SearchInPdfViewController.h +++ b/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/SearchInPdfViewController.h @@ -1,4 +1,3 @@ - #import @class PDFDocument, PDFSelection; diff --git a/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/SearchInPdfViewController.m b/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/SearchInPdfViewController.m index 2af057553e..a7daa1cf22 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/SearchInPdfViewController.m +++ b/iMEGA/Utils/Shared Views/ViewControllers/Document Previewer/SearchInPdfViewController.m @@ -1,4 +1,3 @@ - #import "SearchInPdfViewController.h" #import "NSArray+MNZCategory.h" diff --git a/iMEGA/Utils/Shared Views/ViewControllers/MEGAQLPreviewController.h b/iMEGA/Utils/Shared Views/ViewControllers/MEGAQLPreviewController.h index ef05a11304..9aac04e81f 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/MEGAQLPreviewController.h +++ b/iMEGA/Utils/Shared Views/ViewControllers/MEGAQLPreviewController.h @@ -1,4 +1,3 @@ - #import @interface MEGAQLPreviewController : QLPreviewController diff --git a/iMEGA/Utils/Shared Views/ViewControllers/MEGAQLPreviewController.m b/iMEGA/Utils/Shared Views/ViewControllers/MEGAQLPreviewController.m index ee66348aef..c8a3c2aee9 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/MEGAQLPreviewController.m +++ b/iMEGA/Utils/Shared Views/ViewControllers/MEGAQLPreviewController.m @@ -1,4 +1,3 @@ - #import "MEGAQLPreviewController.h" #import "MEGALinkManager.h" diff --git a/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h b/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h index 5eb182dd75..fb75b3bbe5 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h +++ b/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.h @@ -1,4 +1,3 @@ - #import #import "OnboardingViewType.h" diff --git a/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m b/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m index 9d2a0ce7a2..b725ee03cd 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m +++ b/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingView/OnboardingView.m @@ -1,6 +1,7 @@ - #import "OnboardingView.h" +@import MEGAL10nObjc; + @interface OnboardingView () @property (nonatomic) UIView *customView; @@ -63,47 +64,47 @@ - (void)setType:(OnboardingViewType)type { switch (self.type) { case OnboardingViewTypeEncryptionInfo: self.imageView.image = [UIImage imageNamed:@"onboarding1_encryption"]; - self.titleLabel.text = NSLocalizedString(@"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 = NSLocalizedString(@"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"); + self.titleLabel.text = LocalizedString(@"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 = LocalizedString(@"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:@"onboarding2_chat"]; - self.titleLabel.text = NSLocalizedString(@"Encrypted chat", @"Title shown in a page of the on boarding screens explaining that the chat is encrypted"); - self.descriptionLabel.text = NSLocalizedString(@"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"); + self.titleLabel.text = LocalizedString(@"Encrypted chat", @"Title shown in a page of the on boarding screens explaining that the chat is encrypted"); + self.descriptionLabel.text = LocalizedString(@"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:@"onboarding3_contacts"]; - self.titleLabel.text = NSLocalizedString(@"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 = NSLocalizedString(@"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"); + self.titleLabel.text = LocalizedString(@"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 = LocalizedString(@"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:@"onboarding4_camera_uploads"]; - self.titleLabel.text = NSLocalizedString(@"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 = NSLocalizedString(@"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"); + self.titleLabel.text = LocalizedString(@"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 = LocalizedString(@"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.imageView.contentMode = UIViewContentModeCenter; - self.titleLabel.text = NSLocalizedString(@"Allow Access to Photos", @"Title label that explains that the user is going to be asked for the photos permission"); - self.descriptionLabel.text = NSLocalizedString(@"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"); + self.titleLabel.text = LocalizedString(@"Allow Access to Photos", @"Title label that explains that the user is going to be asked for the photos permission"); + self.descriptionLabel.text = LocalizedString(@"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.imageView.contentMode = UIViewContentModeCenter; - self.titleLabel.text = NSLocalizedString(@"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 = NSLocalizedString(@"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"); + self.titleLabel.text = LocalizedString(@"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 = LocalizedString(@"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:@"notificationDevicePermission"]; self.imageView.contentMode = UIViewContentModeCenter; - self.titleLabel.text = NSLocalizedString(@"Enable Notifications", @"Title label that explains that the user is going to be asked for the notifications permission"); - self.descriptionLabel.text = NSLocalizedString(@"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"); + self.titleLabel.text = LocalizedString(@"Enable Notifications", @"Title label that explains that the user is going to be asked for the notifications permission"); + self.descriptionLabel.text = LocalizedString(@"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/Shared Views/ViewControllers/OnboardingViewController/OnboardingViewController.h b/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingViewController.h index 422f80316d..d2352fa938 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingViewController.h +++ b/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingViewController.h @@ -1,4 +1,3 @@ - #import #import "OnboardingType.h" diff --git a/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingViewController.m b/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingViewController.m index ae8b3348be..c1306d41d5 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingViewController.m +++ b/iMEGA/Utils/Shared Views/ViewControllers/OnboardingViewController/OnboardingViewController.m @@ -1,8 +1,9 @@ - #import "OnboardingViewController.h" #import "OnboardingView.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @interface OnboardingViewController () @property (nonatomic) OnboardingType type; @@ -44,12 +45,12 @@ - (void)viewDidLoad { case OnboardingTypeDefault: [self.pageControl addTarget:self action:@selector(pageControlValueChanged) forControlEvents:UIControlEventValueChanged]; - [self.primaryButton setTitle:NSLocalizedString(@"createAccount", @"Button title which triggers the action to create a MEGA account") forState:UIControlStateNormal]; + [self.primaryButton setTitle:LocalizedString(@"createAccount", @"Button title which triggers the action to create a MEGA account") forState:UIControlStateNormal]; - [self.secondaryButton setTitle:NSLocalizedString(@"login", @"Button title which triggers the action to login in your MEGA account") forState:UIControlStateNormal]; + [self.secondaryButton setTitle:LocalizedString(@"login", @"Button title which triggers the action to login in your MEGA account") forState:UIControlStateNormal]; [self setupTertiaryButton]; - [self.tertiaryButton setTitle:NSLocalizedString(@"general.joinMeetingAsGuest", @"Button title which triggers the action to join meeting as Guest") forState:UIControlStateNormal]; + [self.tertiaryButton setTitle:LocalizedString(@"general.joinMeetingAsGuest", @"Button title which triggers the action to join meeting as Guest") forState:UIControlStateNormal]; if (self.scrollView.subviews.firstObject.subviews.count == 4) { OnboardingView *onboardingViewEncryption = self.scrollView.subviews.firstObject.subviews.firstObject; @@ -69,7 +70,7 @@ - (void)viewDidLoad { self.pageControl.hidden = YES; self.secondaryButton.hidden = YES; self.tertiaryButton.hidden = YES; - [self.primaryButton setTitle:NSLocalizedString(@"continue", @"'Next' button in a dialog") forState:UIControlStateNormal]; + [self.primaryButton setTitle:LocalizedString(@"continue", @"'Next' button in a dialog") forState:UIControlStateNormal]; int nextIndex = 0; @@ -153,7 +154,7 @@ - (void)scrollTo:(NSUInteger)page { OnboardingView *currentView = self.scrollView.subviews.firstObject.subviews[page]; if (currentView.type == OnboardingViewTypeNotificationsPermission) { - [self.primaryButton setTitle:NSLocalizedString(@"continue", @"'Next' button in a dialog") forState:UIControlStateNormal]; + [self.primaryButton setTitle:LocalizedString(@"continue", @"'Next' button in a dialog") forState:UIControlStateNormal]; self.secondaryButton.hidden = YES; } } diff --git a/iMEGA/Utils/Shared Views/ViewControllers/OverDiskQuota/OverDiskQuotaViewController.swift b/iMEGA/Utils/Shared Views/ViewControllers/OverDiskQuota/OverDiskQuotaViewController.swift index a062c34556..2e48414c47 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/OverDiskQuota/OverDiskQuotaViewController.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/OverDiskQuota/OverDiskQuotaViewController.swift @@ -1,4 +1,5 @@ import MEGAFoundation +import MEGAL10n import MEGAUIKit import UIKit diff --git a/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/PasswordReminderViewController.h b/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/PasswordReminderViewController.h index 5e2354b1e1..59bcae7e74 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/PasswordReminderViewController.h +++ b/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/PasswordReminderViewController.h @@ -1,4 +1,3 @@ - #import @interface PasswordReminderViewController : UIViewController diff --git a/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/PasswordReminderViewController.m b/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/PasswordReminderViewController.m index 968f8868f2..219ddb6fad 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/PasswordReminderViewController.m +++ b/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/PasswordReminderViewController.m @@ -1,4 +1,3 @@ - #import "PasswordReminderViewController.h" #import "MEGASdkManager.h" @@ -9,6 +8,8 @@ #import "TestPasswordViewController.h" #import "MEGAGenericRequestDelegate.h" +@import MEGAL10nObjc; + @interface PasswordReminderViewController () @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @@ -53,7 +54,7 @@ - (void)viewDidAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (self.logout) { - self.navigationItem.title = NSLocalizedString(@"Password Reminder", @"Title for feature Password Reminder"); + self.navigationItem.title = LocalizedString(@"Password Reminder", @"Title for feature Password Reminder"); } } @@ -144,22 +145,22 @@ - (void)notifyUserSkippedOrBlockedPasswordReminder { - (void)configureUI { if (self.logout) { - UIBarButtonItem *cancelBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"close", nil) style:UIBarButtonItemStylePlain target:self action:@selector(tapClose:)]; + UIBarButtonItem *cancelBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:LocalizedString(@"close", @"") style:UIBarButtonItemStylePlain target:self action:@selector(tapClose:)]; self.navigationItem.rightBarButtonItem = cancelBarButtonItem; } - self.titleLabel.text = NSLocalizedString(@"remindPasswordTitle", @"Title for Remind Password View, inviting user to test password"); - self.switchInfoLabel.text = NSLocalizedString(@"dontShowAgain", @"Text for don't show again Remind Password View option"); - [self.testPasswordButton setTitle:NSLocalizedString(@"testPassword", @"Label for test password button") forState:UIControlStateNormal]; + self.titleLabel.text = LocalizedString(@"remindPasswordTitle", @"Title for Remind Password View, inviting user to test password"); + self.switchInfoLabel.text = LocalizedString(@"dontShowAgain", @"Text for don't show again Remind Password View option"); + [self.testPasswordButton setTitle:LocalizedString(@"testPassword", @"Label for test password button") forState:UIControlStateNormal]; if (self.isLoggingOut) { - self.descriptionLabel.text = NSLocalizedString(@"remindPasswordLogoutText", @" Text to describe why the user should test his/her password before logging out"); - [self.backupKeyButton setTitle:NSLocalizedString(@"exportRecoveryKey", @"Text 'Export Recovery Key' placed just before two buttons into the 'settings' page to allow see (copy/paste) and export the Recovery Key.") forState:UIControlStateNormal]; - [self.dismissButton setTitle:NSLocalizedString(@"proceedToLogout", @"Title of the button which logs out from your account.") forState:UIControlStateNormal]; + self.descriptionLabel.text = LocalizedString(@"remindPasswordLogoutText", @" Text to describe why the user should test his/her password before logging out"); + [self.backupKeyButton setTitle:LocalizedString(@"exportRecoveryKey", @"Text 'Export Recovery Key' placed just before two buttons into the 'settings' page to allow see (copy/paste) and export the Recovery Key.") forState:UIControlStateNormal]; + [self.dismissButton setTitle:LocalizedString(@"proceedToLogout", @"Title of the button which logs out from your account.") forState:UIControlStateNormal]; } else { - self.descriptionLabel.text = NSLocalizedString(@"remindPasswordText", @"Text for Remind Password View, explainig why user should test password"); - [self.backupKeyButton setTitle:NSLocalizedString(@"backupRecoveryKey", @"Label for recovery key button") forState:UIControlStateNormal]; - [self.dismissButton setTitle:NSLocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible).") forState:UIControlStateNormal]; + self.descriptionLabel.text = LocalizedString(@"remindPasswordText", @"Text for Remind Password View, explainig why user should test password"); + [self.backupKeyButton setTitle:LocalizedString(@"backupRecoveryKey", @"Label for recovery key button") forState:UIControlStateNormal]; + [self.dismissButton setTitle:LocalizedString(@"dismiss", @"Label for any 'Dismiss' button, link, text, title, etc. - (String as short as possible).") forState:UIControlStateNormal]; } } diff --git a/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/TestPasswordViewController.h b/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/TestPasswordViewController.h index a6fe7530b9..a6e7fa1508 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/TestPasswordViewController.h +++ b/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/TestPasswordViewController.h @@ -1,4 +1,3 @@ - #import @interface TestPasswordViewController : UIViewController diff --git a/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/TestPasswordViewController.m b/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/TestPasswordViewController.m index 9e359970c2..03019b7094 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/TestPasswordViewController.m +++ b/iMEGA/Utils/Shared Views/ViewControllers/PasswordReminder/TestPasswordViewController.m @@ -1,4 +1,3 @@ - #import "TestPasswordViewController.h" #import "Helper.h" @@ -15,6 +14,8 @@ #import "MEGAMultiFactorAuthCheckRequestDelegate.h" +@import MEGAL10nObjc; + @interface TestPasswordViewController () @property (weak, nonatomic) IBOutlet UILabel *descriptionLabel; @@ -135,7 +136,7 @@ - (void)updateAppearance { [self.passwordView updateAppearance]; - if (![self.confirmButton.titleLabel.text isEqualToString:NSLocalizedString(@"passwordAccepted", @"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.")]) { + if (![self.confirmButton.titleLabel.text isEqualToString:LocalizedString(@"passwordAccepted", @"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.")]) { [self.confirmButton mnz_setupBasic:self.traitCollection]; } [self.backupKeyButton mnz_setupPrimary:self.traitCollection]; @@ -144,7 +145,7 @@ - (void)updateAppearance { } - (void)configureUI { - self.title = NSLocalizedString(@"testPassword", @"Label for test password button"); + self.title = LocalizedString(@"testPassword", @"Label for test password button"); self.passwordView.passwordTextField.delegate = self; [self arrangeLogoutButton]; @@ -152,22 +153,22 @@ - (void)configureUI { if (self.isLoggingOut) { self.navigationItem.rightBarButtonItem = nil; self.navigationController.navigationBar.topItem.title = @""; - self.descriptionLabel.text = NSLocalizedString(@"testPasswordLogoutText", @"Text that described that you are about to logout remenbering why the user should remenber the password and/or test it"); + self.descriptionLabel.text = LocalizedString(@"testPasswordLogoutText", @"Text that described that you are about to logout remenbering why the user should remenber the password and/or test it"); - [self.backupKeyButton setTitle:NSLocalizedString(@"exportRecoveryKey", @"Text 'Export Recovery Key' placed just before two buttons into the 'settings' page to allow see (copy/paste) and export the Recovery Key.") forState:UIControlStateNormal]; + [self.backupKeyButton setTitle:LocalizedString(@"exportRecoveryKey", @"Text 'Export Recovery Key' placed just before two buttons into the 'settings' page to allow see (copy/paste) and export the Recovery Key.") forState:UIControlStateNormal]; } else { - self.closeBarButton.title = NSLocalizedString(@"close", @"A button label."); - NSString *testPasswordText = NSLocalizedString(@"testPasswordText", @"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."); + self.closeBarButton.title = LocalizedString(@"close", @"A button label."); + NSString *testPasswordText = LocalizedString(@"testPasswordText", @"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."); NSString *learnMoreString = [testPasswordText mnz_stringBetweenString:@"[A]" andString:@"[/A]"]; testPasswordText = [testPasswordText stringByReplacingCharactersInRange:[testPasswordText rangeOfString:learnMoreString] withString:@""]; self.descriptionLabel.text = [testPasswordText mnz_removeWebclientFormatters]; - [self.backupKeyButton setTitle:NSLocalizedString(@"backupRecoveryKey", @"Label for recovery key button") forState:UIControlStateNormal]; + [self.backupKeyButton setTitle:LocalizedString(@"backupRecoveryKey", @"Label for recovery key button") forState:UIControlStateNormal]; } - [self.confirmButton setTitle:NSLocalizedString(@"confirm", @"Title text for the account confirmation.") forState:UIControlStateNormal]; + [self.confirmButton setTitle:LocalizedString(@"confirm", @"Title text for the account confirmation.") forState:UIControlStateNormal]; - [self.logoutButton setTitle:NSLocalizedString(@"proceedToLogout", @"Title to confirm that you want to logout") forState:UIControlStateNormal]; + [self.logoutButton setTitle:LocalizedString(@"proceedToLogout", @"Title to confirm that you want to logout") forState:UIControlStateNormal]; } - (void)arrangeLogoutButton { @@ -191,7 +192,7 @@ - (void)passwordTestFailed { changePasswordVC.twoFactorAuthenticationEnabled = request.flag; MEGANavigationController *navigationController = [[MEGANavigationController alloc] initWithRootViewController:changePasswordVC]; - [navigationController addLeftDismissButtonWithText:NSLocalizedString(@"cancel", @"Button title to cancel something")]; + [navigationController addLeftDismissButtonWithText:LocalizedString(@"cancel", @"Button title to cancel something")]; [UIApplication.mnz_presentingViewController presentViewController:navigationController animated:YES completion:nil]; }]; }]; @@ -204,7 +205,7 @@ - (void)passwordTestSuccess { [self.confirmButton mnz_clearSetup]; [self.confirmButton setTitleColor:UIColor.systemGreenColor forState:UIControlStateNormal]; [self.confirmButton setImage:[UIImage imageNamed:@"contact_request_accept"] forState:UIControlStateNormal]; - [self.confirmButton setTitle:NSLocalizedString(@"passwordAccepted", @"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.") forState:UIControlStateNormal]; + [self.confirmButton setTitle:LocalizedString(@"passwordAccepted", @"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.") forState:UIControlStateNormal]; self.logoutButton.hidden = !self.isLoggingOut; } @@ -214,9 +215,9 @@ - (void)resetUI { self.confirmButton.enabled = YES; if (self.isLoggingOut) { - [self.confirmButton setTitle:NSLocalizedString(@"testPassword", @"Label for test password button") forState:UIControlStateNormal]; + [self.confirmButton setTitle:LocalizedString(@"testPassword", @"Label for test password button") forState:UIControlStateNormal]; } else { - [self.confirmButton setTitle:NSLocalizedString(@"confirm", @"Title text for the account confirmation.") forState:UIControlStateNormal]; + [self.confirmButton setTitle:LocalizedString(@"confirm", @"Title text for the account confirmation.") forState:UIControlStateNormal]; } [self.confirmButton mnz_setupBasic:self.traitCollection]; diff --git a/iMEGA/Utils/Shared Views/ViewControllers/PasteImagePreviewViewController/PasteImagePreviewView.swift b/iMEGA/Utils/Shared Views/ViewControllers/PasteImagePreviewViewController/PasteImagePreviewView.swift index 288884ddaa..c03f6aadf7 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/PasteImagePreviewViewController/PasteImagePreviewView.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/PasteImagePreviewViewController/PasteImagePreviewView.swift @@ -1,4 +1,5 @@ import FlexLayout +import MEGAL10n import UIKit class PasteImagePreviewView: UIView { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/StorageFullModalAlertViewController.swift b/iMEGA/Utils/Shared Views/ViewControllers/StorageFullModalAlertViewController.swift index a5c7a5b907..75ca477700 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/StorageFullModalAlertViewController.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/StorageFullModalAlertViewController.swift @@ -1,4 +1,5 @@ import MEGAFoundation +import MEGAL10n import MEGASwift import UIKit diff --git a/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/CreateTextFileAlertViewController.swift b/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/CreateTextFileAlertViewController.swift index 17af34ff55..3104021add 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/CreateTextFileAlertViewController.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/CreateTextFileAlertViewController.swift @@ -1,3 +1,5 @@ +import MEGAL10n + final class CreateTextFileAlertViewController: UIAlertController { var viewModel: CreateTextFileAlertViewModel! diff --git a/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/TextEditorViewController.swift b/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/TextEditorViewController.swift index 0e5d194ae6..1450dfbcce 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/TextEditorViewController.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/TextEditorViewController.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation final class TextEditorViewController: UIViewController { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/TextEditorViewModel.swift b/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/TextEditorViewModel.swift index c0f763365f..02fed24111 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/TextEditorViewModel.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/TextEditor/TextEditorViewModel.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n import MEGAPresentation enum TextEditorViewAction: ActionType { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/TurnOnNotifications/TurnOnNotificationsModel.swift b/iMEGA/Utils/Shared Views/ViewControllers/TurnOnNotifications/TurnOnNotificationsModel.swift index 66000778d7..5415765ccc 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/TurnOnNotifications/TurnOnNotificationsModel.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/TurnOnNotifications/TurnOnNotificationsModel.swift @@ -1,4 +1,3 @@ - struct TurnOnNotificationsModel: Equatable { let headerImageName: String let title: String diff --git a/iMEGA/Utils/Shared Views/ViewControllers/TurnOnNotifications/TurnOnNotificationsViewModel.swift b/iMEGA/Utils/Shared Views/ViewControllers/TurnOnNotifications/TurnOnNotificationsViewModel.swift index 2f3813199a..ac0e64b780 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/TurnOnNotifications/TurnOnNotificationsViewModel.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/TurnOnNotifications/TurnOnNotificationsViewModel.swift @@ -1,5 +1,6 @@ import Foundation import MEGADomain +import MEGAL10n import MEGAPresentation enum TurnOnNotificationsViewAction: ActionType { diff --git a/iMEGA/Utils/Shared Views/ViewControllers/VerifyEmailViewcontroller/VerifyEmailViewController.swift b/iMEGA/Utils/Shared Views/ViewControllers/VerifyEmailViewcontroller/VerifyEmailViewController.swift index 7063a27c47..17f6508188 100644 --- a/iMEGA/Utils/Shared Views/ViewControllers/VerifyEmailViewcontroller/VerifyEmailViewController.swift +++ b/iMEGA/Utils/Shared Views/ViewControllers/VerifyEmailViewcontroller/VerifyEmailViewController.swift @@ -1,3 +1,5 @@ +import MEGAL10n +import MEGASDKRepo import UIKit class VerifyEmailViewController: UIViewController { @@ -108,20 +110,21 @@ class VerifyEmailViewController: UIViewController { } @objc func checkIfBlocked() { - let whyAmIBlockedRequestDelegate = MEGAGenericRequestDelegate.init { (request, error) in - if error.type == .apiOk && request.number == 0 { - - if MEGASdkManager.sharedMEGASdk().rootNode == nil { - guard let session = SAMKeychain.password(forService: "MEGA", account: "sessionV3") else { return } - let loginRequestDelegate = MEGALoginRequestDelegate.init() - MEGASdkManager.sharedMEGASdk().fastLogin(withSession: session, delegate: loginRequestDelegate) - } - - self.presentedViewController?.dismiss(animated: true, completion: nil) - self.dismiss(animated: true, completion: nil) + let whyAmIBlockedRequestDelegate = RequestDelegate { result in + guard case let .success(request) = result, request.number == 0 else { + return + } + + if MEGASdk.shared.rootNode == nil { + guard let session = SAMKeychain.password(forService: "MEGA", account: "sessionV3") else { return } + let loginRequestDelegate = MEGALoginRequestDelegate.init() + MEGASdk.shared.fastLogin(withSession: session, delegate: loginRequestDelegate) } + + self.presentedViewController?.dismiss(animated: true, completion: nil) + self.dismiss(animated: true, completion: nil) } - MEGASdkManager.sharedMEGASdk().whyAmIBlocked(with: whyAmIBlockedRequestDelegate) + MEGASdk.shared.whyAmIBlocked(with: whyAmIBlockedRequestDelegate) } // MARK: Actions @@ -133,19 +136,19 @@ class VerifyEmailViewController: UIViewController { @IBAction func tapResendButton(_ sender: Any) { if MEGAReachabilityManager.isReachableHUDIfNot() { SVProgressHUD.show() - let resendVerificationEmailDelegate = MEGAGenericRequestDelegate.init { (_, error) in + let resendVerificationEmailDelegate = RequestDelegate(successCodes: [.apiOk, .apiEArgs]) { result in SVProgressHUD.dismiss() - if error.type == .apiOk || error.type == .apiEArgs { + if case .success = result { self.hintLabel.isHidden = false } else { SVProgressHUD.showError(withStatus: Strings.Localizable.EmailAlreadySent.pleaseWaitAFewMinutesBeforeTryingAgain) } } - MEGASdkManager.sharedMEGASdk().resendVerificationEmail(with: resendVerificationEmailDelegate) + MEGASdk.shared.resendVerificationEmail(with: resendVerificationEmailDelegate) } } @IBAction func tapLogoutButton(_ sender: Any) { - MEGASdkManager.sharedMEGASdk().logout() + MEGASdk.shared.logout() } } diff --git a/iMEGA/Utils/Shared Views/ViewModels/ToggleSecureFingerprintFlagUIAlertAdapter.swift b/iMEGA/Utils/Shared Views/ViewModels/ToggleSecureFingerprintFlagUIAlertAdapter.swift index b6a3d0efc7..8c056cd004 100644 --- a/iMEGA/Utils/Shared Views/ViewModels/ToggleSecureFingerprintFlagUIAlertAdapter.swift +++ b/iMEGA/Utils/Shared Views/ViewModels/ToggleSecureFingerprintFlagUIAlertAdapter.swift @@ -1,3 +1,4 @@ +import MEGAL10n import MEGASDKRepo struct ToggleSecureFingerprintFlagUIAlertAdapter { @@ -7,7 +8,7 @@ struct ToggleSecureFingerprintFlagUIAlertAdapter { let alertController = UIAlertController(title: nil, message: "Mandatory Contact Fingerprint Verification \(manager.secureFingerprintStatus())", preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), + alertController.addAction(UIAlertAction(title: Strings.localized("ok", comment: ""), style: .cancel, handler: nil)) UIApplication.mnz_visibleViewController().present(alertController, animated: true) diff --git a/iMEGA/Utils/Shared Views/ViewRouters/ActionWarningViewRouter.swift b/iMEGA/Utils/Shared Views/ViewRouters/ActionWarningViewRouter.swift index b917e3d53c..337c9f7f9a 100644 --- a/iMEGA/Utils/Shared Views/ViewRouters/ActionWarningViewRouter.swift +++ b/iMEGA/Utils/Shared Views/ViewRouters/ActionWarningViewRouter.swift @@ -1,5 +1,7 @@ import MEGADomain +import MEGAL10n import MEGAPresentation +import MEGASDKRepo final class ActionWarningViewRouter: NSObject, Routing { private weak var presenter: UIViewController? diff --git a/iMEGA/Utils/Shared Views/ViewRouters/SharedItemsViewRouter.swift b/iMEGA/Utils/Shared Views/ViewRouters/SharedItemsViewRouter.swift index e00d9e2411..b6623b3e59 100644 --- a/iMEGA/Utils/Shared Views/ViewRouters/SharedItemsViewRouter.swift +++ b/iMEGA/Utils/Shared Views/ViewRouters/SharedItemsViewRouter.swift @@ -1,4 +1,3 @@ - final class SharedItemsViewRouter: NSObject { func showShareFoldersContactView(withNodes nodes: [MEGANode]) { diff --git a/iMEGA/Utils/Shared Views/Views/AwaitingEmailConfirmation/AwaitingEmailConfirmationView.h b/iMEGA/Utils/Shared Views/Views/AwaitingEmailConfirmation/AwaitingEmailConfirmationView.h index d525fdb02a..4689c932ec 100644 --- a/iMEGA/Utils/Shared Views/Views/AwaitingEmailConfirmation/AwaitingEmailConfirmationView.h +++ b/iMEGA/Utils/Shared Views/Views/AwaitingEmailConfirmation/AwaitingEmailConfirmationView.h @@ -1,4 +1,3 @@ - #import @interface AwaitingEmailConfirmationView : UIView diff --git a/iMEGA/Utils/Shared Views/Views/AwaitingEmailConfirmation/AwaitingEmailConfirmationView.m b/iMEGA/Utils/Shared Views/Views/AwaitingEmailConfirmation/AwaitingEmailConfirmationView.m index d35b94bc37..2c5289587f 100644 --- a/iMEGA/Utils/Shared Views/Views/AwaitingEmailConfirmation/AwaitingEmailConfirmationView.m +++ b/iMEGA/Utils/Shared Views/Views/AwaitingEmailConfirmation/AwaitingEmailConfirmationView.m @@ -1,4 +1,3 @@ - #import "AwaitingEmailConfirmationView.h" @implementation AwaitingEmailConfirmationView diff --git a/iMEGA/Utils/Shared Views/Views/BadgeButton.swift b/iMEGA/Utils/Shared Views/Views/BadgeButton.swift index 4cbb58fe11..c78fad92e1 100644 --- a/iMEGA/Utils/Shared Views/Views/BadgeButton.swift +++ b/iMEGA/Utils/Shared Views/Views/BadgeButton.swift @@ -1,3 +1,4 @@ +import MEGAL10n import UIKit final class BadgeButton: UIButton { diff --git a/iMEGA/Utils/Shared Views/Views/CopyableLabel.h b/iMEGA/Utils/Shared Views/Views/CopyableLabel.h index 7b78c442aa..c6bae28ec9 100644 --- a/iMEGA/Utils/Shared Views/Views/CopyableLabel.h +++ b/iMEGA/Utils/Shared Views/Views/CopyableLabel.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Shared Views/Views/CopyableLabel.m b/iMEGA/Utils/Shared Views/Views/CopyableLabel.m index 16048bc4d8..e80a3c9298 100644 --- a/iMEGA/Utils/Shared Views/Views/CopyableLabel.m +++ b/iMEGA/Utils/Shared Views/Views/CopyableLabel.m @@ -1,4 +1,3 @@ - #import "CopyableLabel.h" @implementation CopyableLabel diff --git a/iMEGA/Utils/Shared Views/Views/EmptyState/EmptyStateView+Config.swift b/iMEGA/Utils/Shared Views/Views/EmptyState/EmptyStateView+Config.swift index 21be80edd3..7153a9396f 100644 --- a/iMEGA/Utils/Shared Views/Views/EmptyState/EmptyStateView+Config.swift +++ b/iMEGA/Utils/Shared Views/Views/EmptyState/EmptyStateView+Config.swift @@ -1,3 +1,4 @@ +import MEGAL10n extension EmptyStateView { static func create(for type: EmptyStateType) -> EmptyStateView { diff --git a/iMEGA/Utils/Shared Views/Views/EmptyState/EmptyStateView.h b/iMEGA/Utils/Shared Views/Views/EmptyState/EmptyStateView.h index f74c888637..0dbc9559d7 100644 --- a/iMEGA/Utils/Shared Views/Views/EmptyState/EmptyStateView.h +++ b/iMEGA/Utils/Shared Views/Views/EmptyState/EmptyStateView.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Shared Views/Views/InputView.h b/iMEGA/Utils/Shared Views/Views/InputView.h index c5f0f58750..cc54b6af94 100644 --- a/iMEGA/Utils/Shared Views/Views/InputView.h +++ b/iMEGA/Utils/Shared Views/Views/InputView.h @@ -1,4 +1,3 @@ - #import NS_ASSUME_NONNULL_BEGIN diff --git a/iMEGA/Utils/Shared Views/Views/InputView.m b/iMEGA/Utils/Shared Views/Views/InputView.m index 16c8ae9f66..cd1be83b8f 100644 --- a/iMEGA/Utils/Shared Views/Views/InputView.m +++ b/iMEGA/Utils/Shared Views/Views/InputView.m @@ -1,8 +1,9 @@ - #import "InputView.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @implementation InputView #pragma mark - Initialization @@ -88,11 +89,7 @@ - (void)setIconImage:(UIImage *)iconImage { - (void)setTopLabelTextKey:(NSString *)topLabelTextKey { _topLabelTextKey = topLabelTextKey; -#ifdef TARGET_INTERFACE_BUILDER - self.topLabel.text = [[NSBundle bundleForClass:self.class] localizedStringForKey:self.topLabelTextKey value:nil table:nil]; -#else - self.topLabel.text = NSLocalizedString(self.topLabelTextKey, nil); -#endif + self.topLabel.text = LocalizedString(topLabelTextKey, @""); } @end diff --git a/iMEGA/Utils/Shared Views/Views/PaddingLabel.swift b/iMEGA/Utils/Shared Views/Views/PaddingLabel.swift index 140e5af2f4..1136e458b3 100644 --- a/iMEGA/Utils/Shared Views/Views/PaddingLabel.swift +++ b/iMEGA/Utils/Shared Views/Views/PaddingLabel.swift @@ -1,4 +1,3 @@ - final class PaddingLabel: UILabel { /// The amount of padding for each side in the label. diff --git a/iMEGA/Utils/Shared Views/Views/PasswordStrengthIndicatorView.h b/iMEGA/Utils/Shared Views/Views/PasswordStrengthIndicatorView.h index 845791beb1..4f1d881cde 100644 --- a/iMEGA/Utils/Shared Views/Views/PasswordStrengthIndicatorView.h +++ b/iMEGA/Utils/Shared Views/Views/PasswordStrengthIndicatorView.h @@ -1,4 +1,3 @@ - #import @interface PasswordStrengthIndicatorView : UIView diff --git a/iMEGA/Utils/Shared Views/Views/PasswordStrengthIndicatorView.m b/iMEGA/Utils/Shared Views/Views/PasswordStrengthIndicatorView.m index 97d2c022fb..2b9c2af867 100644 --- a/iMEGA/Utils/Shared Views/Views/PasswordStrengthIndicatorView.m +++ b/iMEGA/Utils/Shared Views/Views/PasswordStrengthIndicatorView.m @@ -1,6 +1,7 @@ - #import "PasswordStrengthIndicatorView.h" +@import MEGAL10nObjc; + @interface PasswordStrengthIndicatorView () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @@ -43,46 +44,46 @@ - (void)updateViewWithPasswordStrength:(PasswordStrength)passwordStrength update switch (passwordStrength) { case PasswordStrengthVeryWeak: self.imageView.image = [UIImage imageNamed:@"indicatorVeryWeak"]; - self.strengthLabel.text = NSLocalizedString(@"veryWeak", @"Label displayed during checking the strength of the password introduced. Represents Very Weak security"); + self.strengthLabel.text = LocalizedString(@"veryWeak", @"Label displayed during checking the strength of the password introduced. Represents Very Weak security"); self.strengthLabel.textColor = UIColor.mnz_redError; if (updateDescription) { - self.strengthDescriptionLabel.text = NSLocalizedString(@"passwordVeryWeakOrWeak", @""); + self.strengthDescriptionLabel.text = LocalizedString(@"passwordVeryWeakOrWeak", @""); } break; case PasswordStrengthWeak: self.imageView.image = [UIImage imageNamed:@"indicatorWeak"]; - self.strengthLabel.text = NSLocalizedString(@"weak", @""); + self.strengthLabel.text = LocalizedString(@"weak", @""); self.strengthLabel.textColor = [UIColor colorWithRed:1.0 green:165.0/255.0 blue:0 alpha:1.0]; if (updateDescription) { - self.strengthDescriptionLabel.text = NSLocalizedString(@"passwordVeryWeakOrWeak", @""); + self.strengthDescriptionLabel.text = LocalizedString(@"passwordVeryWeakOrWeak", @""); } break; case PasswordStrengthMedium: self.imageView.image = [UIImage imageNamed:@"indicatorMedium"]; - self.strengthLabel.text = NSLocalizedString(@"PasswordStrengthMedium", @"Label displayed during checking the strength of the password introduced. Represents Medium security"); + self.strengthLabel.text = LocalizedString(@"PasswordStrengthMedium", @"Label displayed during checking the strength of the password introduced. Represents Medium security"); self.strengthLabel.textColor = UIColor.systemGreenColor; if (updateDescription) { - self.strengthDescriptionLabel.text = NSLocalizedString(@"passwordMedium", @""); + self.strengthDescriptionLabel.text = LocalizedString(@"passwordMedium", @""); } break; case PasswordStrengthGood: self.imageView.image = [UIImage imageNamed:@"indicatorGood"]; - self.strengthLabel.text = NSLocalizedString(@"good", @""); + self.strengthLabel.text = LocalizedString(@"good", @""); self.strengthLabel.textColor = [UIColor colorWithRed:18.0/255.0 green:210.0/255.0 blue:56.0/255.0 alpha:1.0]; if (updateDescription) { - self.strengthDescriptionLabel.text = NSLocalizedString(@"passwordGood", @""); + self.strengthDescriptionLabel.text = LocalizedString(@"passwordGood", @""); } break; case PasswordStrengthStrong: self.imageView.image = [UIImage imageNamed:@"indicatorStrong"]; - self.strengthLabel.text = NSLocalizedString(@"strong", @"Label displayed during checking the strength of the password introduced. Represents Strong security"); + self.strengthLabel.text = LocalizedString(@"strong", @"Label displayed during checking the strength of the password introduced. Represents Strong security"); self.strengthLabel.textColor = [UIColor mnz_blueForTraitCollection:self.traitCollection]; if (updateDescription) { - self.strengthDescriptionLabel.text = NSLocalizedString(@"passwordStrong", @""); + self.strengthDescriptionLabel.text = LocalizedString(@"passwordStrong", @""); } break; } diff --git a/iMEGA/Utils/Shared Views/Views/PasswordView.h b/iMEGA/Utils/Shared Views/Views/PasswordView.h index 25652ad602..0ccb27da3b 100644 --- a/iMEGA/Utils/Shared Views/Views/PasswordView.h +++ b/iMEGA/Utils/Shared Views/Views/PasswordView.h @@ -1,4 +1,3 @@ - #import @protocol PasswordViewDelegate diff --git a/iMEGA/Utils/Shared Views/Views/PasswordView.m b/iMEGA/Utils/Shared Views/Views/PasswordView.m index 52775df63d..947dc4ee69 100644 --- a/iMEGA/Utils/Shared Views/Views/PasswordView.m +++ b/iMEGA/Utils/Shared Views/Views/PasswordView.m @@ -1,8 +1,9 @@ - #import "PasswordView.h" #import "MEGA-Swift.h" +@import MEGAL10nObjc; + @implementation PasswordView - (id)initWithFrame:(CGRect)frame { @@ -65,7 +66,7 @@ - (void)configureSecureTextEntry { } - (void)setErrorState:(BOOL)error { - NSString *text = error ? NSLocalizedString(@"passwordWrong", @"Wrong password") : NSLocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password"); + NSString *text = error ? LocalizedString(@"passwordWrong", @"Wrong password") : LocalizedString(@"passwordPlaceholder", @"Hint text to suggest that the user has to write his password"); [self setErrorState:error withText:text]; } @@ -128,11 +129,7 @@ - (void)setLeftImage:(UIImage *)leftImage { - (void)setTopLabelTextKey:(NSString *)topLabelTextKey { _topLabelTextKey = topLabelTextKey; -#ifdef TARGET_INTERFACE_BUILDER - self.topLabel.text = [[NSBundle bundleForClass:self.class] localizedStringForKey:self.topLabelTextKey value:nil table:nil]; -#else - self.topLabel.text = NSLocalizedString(self.topLabelTextKey, nil); -#endif + self.topLabel.text = LocalizedString(topLabelTextKey, @""); } @end diff --git a/iMEGA/Utils/Shared Views/Widget/QuickAccessWidgetManager.swift b/iMEGA/Utils/Shared Views/Widget/QuickAccessWidgetManager.swift index dc1994dcd5..e8523749da 100644 --- a/iMEGA/Utils/Shared Views/Widget/QuickAccessWidgetManager.swift +++ b/iMEGA/Utils/Shared Views/Widget/QuickAccessWidgetManager.swift @@ -1,4 +1,3 @@ - import Foundation import MEGADomain import MEGAFoundation @@ -10,14 +9,32 @@ class QuickAccessWidgetManager: NSObject { private let recentNodesUseCase: any RecentNodesUseCaseProtocol private let favouriteItemsUseCase: any FavouriteItemsUseCaseProtocol private let favouriteNodesUseCase: any FavouriteNodesUseCaseProtocol - - private let debouncer = Debouncer(delay: 1, dispatchQueue: DispatchQueue.global(qos: .background)) + private let debouncer: Debouncer override init() { self.recentItemsUseCase = RecentItemsUseCase(repo: RecentItemsRepository(store: MEGAStore.shareInstance())) self.recentNodesUseCase = RecentNodesUseCase(repo: RecentNodesRepository.newRepo) self.favouriteItemsUseCase = FavouriteItemsUseCase(repo: FavouriteItemsRepository(store: MEGAStore.shareInstance())) self.favouriteNodesUseCase = FavouriteNodesUseCase(repo: FavouriteNodesRepository.newRepo) + self.debouncer = Debouncer(delay: 1, dispatchQueue: DispatchQueue.global(qos: .background)) + + super.init() + } + + init( + recentItemsUseCase: some RecentItemsUseCaseProtocol = RecentItemsUseCase(repo: RecentItemsRepository(store: MEGAStore.shareInstance())), + recentNodesUseCase: some RecentNodesUseCaseProtocol = RecentNodesUseCase(repo: RecentNodesRepository.newRepo), + favouriteItemsUseCase: some FavouriteItemsUseCaseProtocol = FavouriteItemsUseCase(repo: FavouriteItemsRepository(store: MEGAStore.shareInstance())), + favouriteNodesUseCase: some FavouriteNodesUseCaseProtocol = FavouriteNodesUseCase(repo: FavouriteNodesRepository.newRepo), + debouncer: Debouncer = Debouncer(delay: 1, dispatchQueue: DispatchQueue.global(qos: .background)) + ) { + self.recentItemsUseCase = recentItemsUseCase + self.recentNodesUseCase = recentNodesUseCase + self.favouriteItemsUseCase = favouriteItemsUseCase + self.favouriteNodesUseCase = favouriteNodesUseCase + self.debouncer = debouncer + + super.init() } @objc public static func reloadAllWidgetsContent() { @@ -25,66 +42,81 @@ class QuickAccessWidgetManager: NSObject { WidgetCenter.shared.reloadAllTimelines() #endif } - + @objc public static func reloadWidgetContentOfKind(kind: String) { #if arch(arm64) || arch(i386) || arch(x86_64) WidgetCenter.shared.reloadTimelines(ofKind: kind) #endif } - + @objc func createWidgetItemData() { self.createRecentItemsDataWithDebounce() self.createFavouritesItemsDataWithDebounce() } - + @objc func createQuickAccessWidgetItemsDataIfNeeded(for nodeList: MEGANodeList) { guard let nodes = nodeList.mnz_nodesArrayFromNodeList() else { return } - + var shouldCreateRecentItems = false var shouldCreateFavouriteItems = false - + for node in nodes { if (node.isFolder() && node.hasChangedType(.new)) || node.hasChangedType(.removed) { shouldCreateRecentItems = false } else { shouldCreateRecentItems = true } - + if node.hasChangedType(.attributes) { shouldCreateFavouriteItems = true } } - + if shouldCreateRecentItems { self.createRecentItemsDataWithDebounce() } - + if shouldCreateFavouriteItems { self.createFavouritesItemsDataWithDebounce() } } - - @objc func insertFavouriteItem(for node: MEGANode) { + + @objc func updateFavouritesWidget(for nodeList: MEGANodeList) { + nodeList.toNodeArray().forEach { node in + guard node.hasChangedType(.favourite) else { + return + } + + if node.isFavourite { + insertFavouriteItem(for: node) + } else { + deleteFavouriteItem(for: node) + } + } + } + + // MARK: - Private + + private func insertFavouriteItem(for node: MEGANode) { guard let base64Handle = node.base64Handle, let name = node.name else { return } favouriteItemsUseCase.insertFavouriteItem(FavouriteItemEntity(base64Handle: base64Handle, name: name, timestamp: Date())) QuickAccessWidgetManager.reloadWidgetContentOfKind(kind: MEGAFavouritesQuickAccessWidget) } - - @objc func deleteFavouriteItem(for node: MEGANode) { + + private func deleteFavouriteItem(for node: MEGANode) { guard let base64Handle = node.base64Handle else { return } favouriteItemsUseCase.deleteFavouriteItem(with: base64Handle) QuickAccessWidgetManager.reloadWidgetContentOfKind(kind: MEGAFavouritesQuickAccessWidget) } - - // MARK: - Private + private func createRecentItemsDataWithDebounce() { debouncer.start { self.createRecentItemsData() } } - + private func createRecentItemsData() { Task { do { @@ -108,13 +140,13 @@ class QuickAccessWidgetManager: NSObject { } } } - + private func createFavouritesItemsDataWithDebounce() { debouncer.start { self.createFavouritesItemsData() } } - + private func createFavouritesItemsData() { favouriteNodesUseCase.getFavouriteNodes(limitCount: MEGAQuickAccessWidgetMaxDisplayItems) { (result) in switch result { diff --git a/iMEGA/Utils/SharedItems/SharedItemsSearchOperation.swift b/iMEGA/Utils/SharedItems/SharedItemsSearchOperation.swift index 015694c581..785a1bdf07 100644 --- a/iMEGA/Utils/SharedItems/SharedItemsSearchOperation.swift +++ b/iMEGA/Utils/SharedItems/SharedItemsSearchOperation.swift @@ -6,9 +6,9 @@ final class SharedItemsSearchOperation: MEGAOperation { private let text: String private let cancelToken: MEGACancelToken private let sortType: SortOrderEntity - private let completion: (Result<[NodeEntity], Error>) -> Void + private let completion: (Result<[NodeEntity], any Error>) -> Void - init(sdk: MEGASdk, type: SearchNodeTypeEntity, text: String, cancelToken: MEGACancelToken, sortType: SortOrderEntity, completion: @escaping (Result<[NodeEntity], Error>) -> Void) { + init(sdk: MEGASdk, type: SearchNodeTypeEntity, text: String, cancelToken: MEGACancelToken, sortType: SortOrderEntity, completion: @escaping (Result<[NodeEntity], any Error>) -> Void) { self.sdk = sdk self.type = type self.text = text @@ -42,7 +42,7 @@ final class SharedItemsSearchOperation: MEGAOperation { finishOperation(nodes: nodeList.toNodeEntities(), error: nil) } - private func finishOperation(nodes: [NodeEntity]?, error: Error?) { + private func finishOperation(nodes: [NodeEntity]?, error: (any Error)?) { if let error { completion(.failure(error)) } else { diff --git a/iMEGA/Utils/SwiftUI/PermissionAlertViewModifier/PermissionAlertModel.swift b/iMEGA/Utils/SwiftUI/PermissionAlertViewModifier/PermissionAlertModel.swift new file mode 100644 index 0000000000..b600c72cd7 --- /dev/null +++ b/iMEGA/Utils/SwiftUI/PermissionAlertViewModifier/PermissionAlertModel.swift @@ -0,0 +1,44 @@ +import MEGAL10n + +struct PermissionAlertModel { + let title: String + let message: String + let primaryAction: ActionModel + let secondaryAction: ActionModel? + + struct ActionModel { + let title: String + let style: ActionStyle + let handler: (() -> Void)? + } + + enum ActionStyle { + case `default`, cancel, destructive + } +} + +extension PermissionAlertModel { + + static func photo(completion: @escaping () -> Void) -> PermissionAlertModel { + model(message: Strings.Localizable.photoLibraryPermissions, completion: completion) + } + + static func video(completion: @escaping () -> Void) -> PermissionAlertModel { + model( + message: Strings.Localizable.cameraPermissions, + completion: completion + ) + } + + private static func model( + with title: String = Strings.Localizable.attention, + message: String, + completion: @escaping () -> Void + ) -> PermissionAlertModel { + .init( + title: title, + message: message, + primaryAction: .init(title: Strings.Localizable.notNow, style: .cancel, handler: nil), + secondaryAction: .init(title: Strings.Localizable.settingsTitle, style: .default, handler: completion)) + } +} diff --git a/iMEGA/Utils/SwiftUI/PermissionAlertViewModifier/PermissionAlertViewModifier.swift b/iMEGA/Utils/SwiftUI/PermissionAlertViewModifier/PermissionAlertViewModifier.swift new file mode 100644 index 0000000000..531d7cce1a --- /dev/null +++ b/iMEGA/Utils/SwiftUI/PermissionAlertViewModifier/PermissionAlertViewModifier.swift @@ -0,0 +1,64 @@ +import Foundation +import MEGAL10n +import MEGASwiftUI +import SwiftUI + +struct PermissionAlertViewModifier: ViewModifier { + + let isPresented: Binding + let viewModel: PermissionAlertModel + + func body(content: Content) -> some View { + content + .alert(isPresented: isPresented) { + if let secondaryAction = viewModel.secondaryAction { + return Alert(title: Text(viewModel.title), + message: Text(viewModel.message), + primaryButton: alertButton(for: viewModel.primaryAction), + secondaryButton: alertButton(for: secondaryAction)) + + } else { + return Alert(title: Text(viewModel.title), + message: Text(viewModel.message), + dismissButton: .cancel(Text(Strings.Localizable.AlbumLink.InvalidAlbum.Alert.dissmissButtonTitle))) + } + } + } + + private func alertButton(for action: PermissionAlertModel.ActionModel) -> Alert.Button { + switch action.style { + case .default: + return .default(Text(action.title), action: action.handler) + case .cancel: + return .cancel(Text(action.title), action: action.handler) + case .destructive: + return .destructive(Text(action.title), action: action.handler) + } + } +} + +extension View { + + func alertPhotosPermission(isPresented: Binding) -> some View { + permissionAlert(isPresented: isPresented, viewModel: .photo(completion: openSettings)) + } + + func alertVideoPermission(isPresented: Binding) -> some View { + permissionAlert(isPresented: isPresented, viewModel: .video(completion: openSettings)) + } + + private var openSettings: () -> Void { + { + guard let url = URL(string: UIApplication.openSettingsURLString) else { + return + } + UIApplication.shared.open(url) + } + } + + private func permissionAlert(isPresented: Binding, viewModel: PermissionAlertModel) -> some View { + modifier(PermissionAlertViewModifier( + isPresented: isPresented, + viewModel: viewModel)) + } +} diff --git a/iMEGA/Utils/SwiftUI/WarningView/WarningViewModel.swift b/iMEGA/Utils/SwiftUI/WarningView/WarningViewModel.swift index 73d5d0b478..26e5c18e0b 100644 --- a/iMEGA/Utils/SwiftUI/WarningView/WarningViewModel.swift +++ b/iMEGA/Utils/SwiftUI/WarningView/WarningViewModel.swift @@ -1,3 +1,5 @@ +import MEGAL10n + enum WarningType: CustomStringConvertible { case noInternetConnection case limitedPhotoAccess diff --git a/iMEGA/Utils/Tab.swift b/iMEGA/Utils/Tab.swift index 3f3a15eaae..6b64dc4d8e 100644 --- a/iMEGA/Utils/Tab.swift +++ b/iMEGA/Utils/Tab.swift @@ -1,4 +1,5 @@ import MEGADomain +import MEGAL10n @objc enum TabType: Int, CaseIterable { case cloudDrive diff --git a/iMEGA/Utils/TransferRecordDTO.swift b/iMEGA/Utils/TransferRecordDTO.swift index f6a698c273..6a85426886 100644 --- a/iMEGA/Utils/TransferRecordDTO.swift +++ b/iMEGA/Utils/TransferRecordDTO.swift @@ -1,4 +1,3 @@ - @objc class TransferRecordDTO: NSObject { @objc let localIdentifier: String @objc let parentNodeHandle: NSNumber diff --git a/jenkinsfile/ios_automation_app_upload.groovy b/jenkinsfile/ios_automation_app_upload.groovy index 4210497366..75d408b1a8 100644 --- a/jenkinsfile/ios_automation_app_upload.groovy +++ b/jenkinsfile/ios_automation_app_upload.groovy @@ -10,7 +10,7 @@ def injectEnvironments(Closure body) { } pipeline { - agent { label 'mac-jenkins-slave-ios' } + agent { label 'macstudio1' } options { timeout(time: 1, unit: 'HOURS') gitLabConnection('GitLabConnection') diff --git a/jenkinsfile/ios_build_status.groovy b/jenkinsfile/ios_build_status.groovy index f8cf47639b..684d6bdd16 100644 --- a/jenkinsfile/ios_build_status.groovy +++ b/jenkinsfile/ios_build_status.groovy @@ -10,7 +10,7 @@ def injectEnvironments(Closure body) { } pipeline { - agent { label 'mac-jenkins-slave-ios' } + agent { label 'macstudio1' } options { timeout(time: 1, unit: 'HOURS') gitLabConnection('GitLabConnection') diff --git a/jenkinsfile/ios_submit_for_review.groovy b/jenkinsfile/ios_submit_for_review.groovy index 685daa16e7..ebadcc3eff 100644 --- a/jenkinsfile/ios_submit_for_review.groovy +++ b/jenkinsfile/ios_submit_for_review.groovy @@ -9,7 +9,7 @@ def injectEnvironments(Closure body) { } pipeline { - agent { label 'mac-jenkins-slave-ios' } + agent { label 'macstudio1' } options { timeout(time: 1, unit: 'HOURS') gitLabConnection('GitLabConnection') diff --git a/jenkinsfile/ios_upload.groovy b/jenkinsfile/ios_upload.groovy index 230e1b9e04..0461af36b2 100644 --- a/jenkinsfile/ios_upload.groovy +++ b/jenkinsfile/ios_upload.groovy @@ -9,7 +9,7 @@ def injectEnvironments(Closure body) { } pipeline { - agent { label 'mac-jenkins-slave-ios' } + agent { label 'macstudio1' } options { timeout(time: 3, unit: 'HOURS') gitLabConnection('GitLabConnection') diff --git a/scripts/boot-simulators.sh b/scripts/boot-simulators.sh index 3a8f474cce..ca2a3d1cd1 100755 --- a/scripts/boot-simulators.sh +++ b/scripts/boot-simulators.sh @@ -25,5 +25,4 @@ boot_simulator() { fi } -boot_simulator "iPhone 14 Pro Max" -# boot_simulator "iPhone 14 Pro" \ No newline at end of file +boot_simulator "iPhone 15 Pro Max" \ No newline at end of file diff --git a/scripts/check_translations.py b/scripts/check_translations.py index 4159aef590..d31dfa1728 100755 --- a/scripts/check_translations.py +++ b/scripts/check_translations.py @@ -37,14 +37,14 @@ def checkIfKeyIsPresent(key, filePath): print("Loading base file") -baseKeys = getKeysInFile("./../iMEGA/Languages/Base.lproj/Localizable.strings") +baseKeys = getKeysInFile("./../Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/Base.lproj/Localizable.strings") missingKeys = {} for baseKey in baseKeys: print("scanning key: " + baseKey) - directories = glob.glob("./../iMEGA/Languages/*.lproj") - directories.remove('./../iMEGA/Languages/Base.lproj') + directories = glob.glob("./../Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/*.lproj") + directories.remove('./../Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/Base.lproj') missingFiles = [] for languageDir in directories: diff --git a/transifex/iosTransifex.py b/transifex/iosTransifex.py index d41c78fb93..1260e3a69f 100755 --- a/transifex/iosTransifex.py +++ b/transifex/iosTransifex.py @@ -41,7 +41,7 @@ sys.exit(1) BASE_URL = "https://rest.api.transifex.com" -GITLAB_URL = "https://code.developers.mega.co.nz/api/v4/projects/193/repository/files/iMEGA%2FLanguages%2FBase.lproj%2F$file/raw?ref=develop" +GITLAB_URL = "https://code.developers.mega.co.nz/api/v4/projects/193/repository/files/Modules%2FLocalization%2FMEGAL10n%2FSources%2FMEGAL10n%2FResources%2FBase.lproj%2F$file/raw?ref=develop" PROJECT_ID = "o:meganz-1:p:ios-35" STORES_IOS_ID = "o:meganz-1:p:stores:r:app_store_ios" HEADER = { @@ -58,7 +58,7 @@ git_path = os.getcwd() if "/transifex" in git_path: git_path = git_path + "/.." -PROD_FOLDER = git_path + "/iMEGA/Languages/" +PROD_FOLDER = git_path + "/Modules/Localization/MEGAL10n/Sources/MEGAL10n/Resources/" if not os.path.isdir(PROD_FOLDER): os.makedirs(PROD_FOLDER) if not os.path.isdir(DOWNLOAD_FOLDER): @@ -1463,9 +1463,11 @@ def store_file(resource, content, lang = "Base"): file_path = PROD_FOLDER if "LTHPasscodeViewController" in resource: if lang == "Base": - file_path += "../Vendor/LTHPasscodeViewController/Localizations/LTHPasscodeViewController.bundle/" + lang + ".lproj/LTHPasscodeViewController.strings" + file_path = git_path + "/iMEGA/Vendor/LTHPasscodeViewController/Localizations/LTHPasscodeViewController.bundle/" + lang + ".lproj/LTHPasscodeViewController.strings" else: file_path = DOWNLOAD_FOLDER + "LTHPasscodeViewController.strings-" + lang + elif "InfoPlist" in resource: + file_path = git_path + "/iMEGA/Languages/" + lang + ".lproj/" + get_file_basename(resource) elif "Changelogs" in resource: file_path = DOWNLOAD_FOLDER + "Changelogs.strings-" + lang else: @@ -1583,11 +1585,13 @@ def main(): resource = args.resource[0] file_name = get_file_basename(resource) if "LTHPasscodeViewController" in resource: - file_path = PROD_FOLDER + "../Vendor/LTHPasscodeViewController/Localizations/LTHPasscodeViewController.bundle/Base.lproj/" + file_name + file_path = git_path + "/iMEGA/Vendor/LTHPasscodeViewController/Localizations/LTHPasscodeViewController.bundle/Base.lproj/" + file_name branch = False elif "Changelogs" in resource: file_path = DOWNLOAD_FOLDER + "Changelogs.strings-Base" branch = False + elif "InfoPlist" in resource: + file_path = git_path + "/iMEGA/Languages/" + "Base.lproj/" + file_name else: file_path = PROD_FOLDER + "Base.lproj/" + file_name if args.file: @@ -1677,11 +1681,13 @@ def main(): resource = args.resource[0] file_name = get_file_basename(resource) if "LTHPasscodeViewController" in resource: - file_path = PROD_FOLDER + "../Vendor/LTHPasscodeViewController/Localizations/LTHPasscodeViewController.bundle/Base.lproj/" + file_name + file_path = git_path + "/iMEGA/Vendor/LTHPasscodeViewController/Localizations/LTHPasscodeViewController.bundle/Base.lproj/" + file_name branch = False elif "Changelogs" in resource: file_path = DOWNLOAD_FOLDER + "Changelogs.strings-Base" branch = False + elif "InfoPlist" in resource: + file_path = git_path + "/iMEGA/Languages/" + "Base.lproj/" + file_name else: file_path = PROD_FOLDER + "Base.lproj/" + file_name if args.file: