From 7de1b7e5b9adfdb47a3365b236c1ffef56d150f0 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 21 Jun 2023 14:02:50 +0100 Subject: [PATCH 01/82] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index b484e08dc..46c0099fd 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.10.14 -CURRENT_PROJECT_VERSION = 1.10.14 +MARKETING_VERSION = 1.10.15 +CURRENT_PROJECT_VERSION = 1.10.15 From 3f3a92dbf3862d2c64d81cb0e503545939a680f7 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:26:43 +0100 Subject: [PATCH 02/82] Disable Siri, Share and Reply (from NSE) Extensions. (#7619) --- Podfile | 24 +++++++++++---------- Podfile.lock | 2 +- Riot/SupportingFiles/Riot-Bridging-Header.h | 2 +- Riot/target.yml | 6 ++++-- RiotNSE/NotificationService.swift | 4 +++- changelog.d/7618.change | 1 + project.yml | 6 ++++-- 7 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 changelog.d/7618.change diff --git a/Podfile b/Podfile index 4aabb6bbc..fda86b718 100644 --- a/Podfile +++ b/Podfile @@ -104,22 +104,12 @@ abstract_target 'RiotPods' do end end - target "RiotShareExtension" do - import_MatrixSDK - import_MatrixKit_pods - end - target "RiotSwiftUI" do import_SwiftUI_pods - end + end target "RiotSwiftUITests" do import_SwiftUI_pods - end - - target "SiriIntents" do - import_MatrixSDK - import_MatrixKit_pods end target "RiotNSE" do @@ -131,6 +121,18 @@ abstract_target 'RiotPods' do import_MatrixSDK end + # Disabled due to crypto corruption issues. + # https://github.com/vector-im/element-ios/issues/7618 + # target "RiotShareExtension" do + # import_MatrixSDK + # import_MatrixKit_pods + # end + # + # target "SiriIntents" do + # import_MatrixSDK + # import_MatrixKit_pods + # end + end post_install do |installer| diff --git a/Podfile.lock b/Podfile.lock index bdbbb2d00..fb9bd48ae 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -208,6 +208,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 0e7e10f516d40d9df60cb874170b91603c632118 +PODFILE CHECKSUM: f8d711a31048bfd5981d17b00487a85996b62983 COCOAPODS: 1.11.3 diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index 296545a4e..8c2df6f56 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -44,7 +44,7 @@ #import "ContactDetailsViewController.h" #import "RoomInputToolbarView.h" #import "NSArray+Element.h" -#import "ShareItemSender.h" +#import "ShareItemSenderProtocol.h" #import "Contact.h" #import "RoomTimelineCellProvider.h" #import "PlainRoomTimelineCellProvider.h" diff --git a/Riot/target.yml b/Riot/target.yml index 8139be715..929af6e3a 100644 --- a/Riot/target.yml +++ b/Riot/target.yml @@ -34,8 +34,10 @@ targets: platform: iOS dependencies: - - target: RiotShareExtension - - target: SiriIntents + # Disabled due to crypto corruption issues. + # https://github.com/vector-im/element-ios/issues/7618 + # - target: RiotShareExtension + # - target: SiriIntents - target: RiotNSE - target: BroadcastUploadExtension - target: DesignKit diff --git a/RiotNSE/NotificationService.swift b/RiotNSE/NotificationService.swift index 1f953583b..6c41f34c6 100644 --- a/RiotNSE/NotificationService.swift +++ b/RiotNSE/NotificationService.swift @@ -799,7 +799,9 @@ class NotificationService: UNNotificationServiceExtension { return Constants.toBeRemovedNotificationCategoryIdentifier } - return "QUICK_REPLY" + // Don't return QUICK_REPLY here as there is an issue + // with crypto corruption when sending from extensions. + return nil } /// Attempts to send trigger a VoIP push for the given event diff --git a/changelog.d/7618.change b/changelog.d/7618.change new file mode 100644 index 000000000..0cefa0983 --- /dev/null +++ b/changelog.d/7618.change @@ -0,0 +1 @@ +Disable Siri, Share and Reply (from NSE) Extensions. \ No newline at end of file diff --git a/project.yml b/project.yml index 8cbc73192..430b39cbc 100644 --- a/project.yml +++ b/project.yml @@ -29,8 +29,6 @@ options: include: - path: Riot/target.yml - path: RiotTests/target.yml - - path: RiotShareExtension/target.yml - - path: SiriIntents/target.yml - path: RiotNSE/target.yml - path: BroadcastUploadExtension/target.yml - path: DesignKit/target.yml @@ -39,6 +37,10 @@ include: - path: RiotSwiftUI/targetUITests.yml - path: CommonKit/target.yml - path: CommonKit/targetUnitTests.yml + # Disabled due to crypto corruption issues. + # https://github.com/vector-im/element-ios/issues/7618 + # - path: RiotShareExtension/target.yml + # - path: SiriIntents/target.yml packages: AnalyticsEvents: From f8716cb929b2336b650861e2b12bbb7794e28ec4 Mon Sep 17 00:00:00 2001 From: Laurence Gill Date: Wed, 12 Jul 2023 12:14:28 +0100 Subject: [PATCH 03/82] Update posthog URL to posthog.element.io (#7620) --- Config/BuildSettings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index f58f969c1..faf730a90 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -192,7 +192,7 @@ final class BuildSettings: NSObject { #else /// The configuration to use for analytics. Set `isEnabled` to false to disable analytics. static let analyticsConfiguration = AnalyticsConfiguration(isEnabled: BuildSettings.baseBundleIdentifier.starts(with: "im.vector.app"), - host: "https://posthog.hss.element.io", + host: "https://posthog.element.io", apiKey: "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO", termsURL: URL(string: "https://element.io/cookie-policy")!) #endif From 6c71cab7f480ed027f24fe0d31c571dc2597db50 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Fri, 4 Aug 2023 12:22:14 +0100 Subject: [PATCH 04/82] Fix a crash when opening v11 rooms. (#7635) https://github.com/matrix-org/matrix-spec-proposals/pull/2175 --- .../Utils/EventFormatter/MXKEventFormatter.m | 46 ++++++++++--------- changelog.d/7633.bugfix | 1 + 2 files changed, 26 insertions(+), 21 deletions(-) create mode 100644 changelog.d/7633.bugfix diff --git a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m index 19fae141d..7ae98184a 100644 --- a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m +++ b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m @@ -833,35 +833,39 @@ static NSString *const kRepliedTextPattern = @".*
.*
(.* NSString *creatorId; MXJSONModelSetString(creatorId, event.content[@"creator"]); - if (creatorId) + if (!creatorId) { - if ([creatorId isEqualToString:mxSession.myUserId]) + // Room version 11 removes `creator` in favour of `sender`. + // https://github.com/matrix-org/matrix-spec-proposals/pull/2175 + creatorId = event.sender; + } + + if ([creatorId isEqualToString:mxSession.myUserId]) + { + if (isRoomDirect) { - if (isRoomDirect) - { - displayText = [VectorL10n noticeRoomCreatedByYouForDm]; - } - else - { - displayText = [VectorL10n noticeRoomCreatedByYou]; - } + displayText = [VectorL10n noticeRoomCreatedByYouForDm]; } else { - if (isRoomDirect) - { - displayText = [VectorL10n noticeRoomCreatedForDm:(roomState ? [roomState.members memberName:creatorId] : creatorId)]; - } - else - { - displayText = [VectorL10n noticeRoomCreated:(roomState ? [roomState.members memberName:creatorId] : creatorId)]; - } + displayText = [VectorL10n noticeRoomCreatedByYou]; } - // Append redacted info if any - if (redactedInfo) + } + else + { + if (isRoomDirect) { - displayText = [NSString stringWithFormat:@"%@ %@", displayText, redactedInfo]; + displayText = [VectorL10n noticeRoomCreatedForDm:(roomState ? [roomState.members memberName:creatorId] : creatorId)]; } + else + { + displayText = [VectorL10n noticeRoomCreated:(roomState ? [roomState.members memberName:creatorId] : creatorId)]; + } + } + // Append redacted info if any + if (redactedInfo) + { + displayText = [NSString stringWithFormat:@"%@ %@", displayText, redactedInfo]; } break; } diff --git a/changelog.d/7633.bugfix b/changelog.d/7633.bugfix new file mode 100644 index 000000000..b7345971e --- /dev/null +++ b/changelog.d/7633.bugfix @@ -0,0 +1 @@ +Fix a crash when opening v11 rooms. \ No newline at end of file From 5a536db324ff39f517b5a459c98a520ce21d0185 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:15:03 +0100 Subject: [PATCH 05/82] Fix SSO generating invalid URLs. (#7639) --- Riot/Modules/Authentication/SSO/SSOAuthenticationService.swift | 2 +- changelog.d/pr-7639.bugfix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/pr-7639.bugfix diff --git a/Riot/Modules/Authentication/SSO/SSOAuthenticationService.swift b/Riot/Modules/Authentication/SSO/SSOAuthenticationService.swift index a0282c742..706c3782d 100644 --- a/Riot/Modules/Authentication/SSO/SSOAuthenticationService.swift +++ b/Riot/Modules/Authentication/SSO/SSOAuthenticationService.swift @@ -50,7 +50,7 @@ final class SSOAuthenticationService: NSObject { var ssoRedirectPath = SSOURLConstants.Paths.redirect - if let identityProvider = identityProvider { + if let identityProvider = identityProvider, !identityProvider.isEmpty { ssoRedirectPath.append("/\(identityProvider)") } diff --git a/changelog.d/pr-7639.bugfix b/changelog.d/pr-7639.bugfix new file mode 100644 index 000000000..9bbf73b23 --- /dev/null +++ b/changelog.d/pr-7639.bugfix @@ -0,0 +1 @@ +Fix bug in SSO URL generation that was non-compliant with the spec. \ No newline at end of file From 945ec64230d648dada4a83a98ec7b0c44f504001 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Wed, 9 Aug 2023 18:18:33 +0100 Subject: [PATCH 06/82] Only use the sender when rendering the room creation event. (#7640) On Room v11 it is possible that the sender could put in a fake creator. Don't allow this to happen. --- .../Utils/EventFormatter/MXKEventFormatter.m | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m index 7ae98184a..d6a3b5cfd 100644 --- a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m +++ b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m @@ -830,15 +830,10 @@ static NSString *const kRepliedTextPattern = @".*
.*
(.* } case MXEventTypeRoomCreate: { - NSString *creatorId; - MXJSONModelSetString(creatorId, event.content[@"creator"]); - - if (!creatorId) - { - // Room version 11 removes `creator` in favour of `sender`. - // https://github.com/matrix-org/matrix-spec-proposals/pull/2175 - creatorId = event.sender; - } + // Room version 11 removes `creator` in favour of `sender`. + // https://github.com/matrix-org/matrix-spec-proposals/pull/2175 + // Just use the sender as it is possible to create a v11 room and spoof the `creator`. + NSString *creatorId = event.sender; if ([creatorId isEqualToString:mxSession.myUserId]) { From ef94fd8fccf4386815c8ef0505c1391ced49bb70 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 27 Jul 2023 16:03:13 +0300 Subject: [PATCH 07/82] Device dehydration v2 - add support for device dehydration v2 i.e. `org.matrix.msc3814` - run dehydration flows after successfully recovering or creating secrets - enable said flows based on .well-known `org.matrix.msc3814` config key - delete previous implementation and helper methods --- .../HomeserverConfigurationBuilder.swift | 5 +- .../HomeserverEncryptionConfiguration.swift | 5 +- .../MXKAuthenticationViewController.h | 10 --- .../MXKAuthenticationViewController.m | 62 ------------------ .../MatrixKit/Models/Account/MXKAccount.h | 7 -- .../MatrixKit/Models/Account/MXKAccount.m | 64 ------------------- .../Models/Account/MXKAccountManager.h | 2 - .../Models/Account/MXKAccountManager.m | 1 - .../SecretsRecoveryWithKeyCoordinator.swift | 4 +- .../SecretsRecoveryWithKeyViewModel.swift | 9 ++- ...etsRecoveryWithPassphraseCoordinator.swift | 4 +- ...cretsRecoveryWithPassphraseViewModel.swift | 9 ++- .../Recover/SecretsRecoveryCoordinator.swift | 18 +++++- .../SecretsSetupRecoveryKeyCoordinator.swift | 9 ++- .../SecretsSetupRecoveryKeyViewModel.swift | 8 ++- .../Setup/SecureBackupSetupCoordinator.swift | 15 ++++- 16 files changed, 72 insertions(+), 160 deletions(-) diff --git a/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift b/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift index 1b7805223..af4beb29e 100644 --- a/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift +++ b/Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift @@ -50,11 +50,14 @@ final class HomeserverConfigurationBuilder: NSObject { } else { secureBackupSetupMethods = VectorWellKnownBackupSetupMethod.allCases } + + let deviceDehydrationEnabled = wellKnown?.jsonDictionary()["org.matrix.msc3814"] as? Bool == true let encryptionConfiguration = HomeserverEncryptionConfiguration(isE2EEByDefaultEnabled: isE2EEByDefaultEnabled, isSecureBackupRequired: isSecureBackupRequired, secureBackupSetupMethods: secureBackupSetupMethods, - outboundKeysPreSharingMode: outboundKeysPreSharingMode) + outboundKeysPreSharingMode: outboundKeysPreSharingMode, + deviceDehydrationEnabled: deviceDehydrationEnabled) // Jitsi configuration let jitsiPreferredDomain: String? diff --git a/Riot/Model/HomeserverConfiguration/HomeserverEncryptionConfiguration.swift b/Riot/Model/HomeserverConfiguration/HomeserverEncryptionConfiguration.swift index 6672bff45..671756f63 100644 --- a/Riot/Model/HomeserverConfiguration/HomeserverEncryptionConfiguration.swift +++ b/Riot/Model/HomeserverConfiguration/HomeserverEncryptionConfiguration.swift @@ -23,15 +23,18 @@ final class HomeserverEncryptionConfiguration: NSObject { let isSecureBackupRequired: Bool let secureBackupSetupMethods: [VectorWellKnownBackupSetupMethod] let outboundKeysPreSharingMode: MXKKeyPreSharingStrategy + let deviceDehydrationEnabled: Bool init(isE2EEByDefaultEnabled: Bool, isSecureBackupRequired: Bool, secureBackupSetupMethods: [VectorWellKnownBackupSetupMethod], - outboundKeysPreSharingMode: MXKKeyPreSharingStrategy) { + outboundKeysPreSharingMode: MXKKeyPreSharingStrategy, + deviceDehydrationEnabled: Bool) { self.isE2EEByDefaultEnabled = isE2EEByDefaultEnabled self.isSecureBackupRequired = isSecureBackupRequired self.outboundKeysPreSharingMode = outboundKeysPreSharingMode self.secureBackupSetupMethods = secureBackupSetupMethods + self.deviceDehydrationEnabled = deviceDehydrationEnabled super.init() } diff --git a/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.h index 4b218086f..bbfbfed06 100644 --- a/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.h +++ b/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.h @@ -305,15 +305,5 @@ */ - (void)showAuthenticationFallBackView; -#pragma mark - Device rehydration - -/** - Call this method at an appropriate time to attempt rehydrating from an existing dehydrated device - @param keyData Secret key data - @param credentials Account credentials - */ - -- (void)attemptDeviceRehydrationWithKeyData:(NSData *)keyData credentials:(MXCredentials *)credentials; - @end diff --git a/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.m index 1ec7972a0..9d0f01487 100644 --- a/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.m +++ b/Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.m @@ -1495,68 +1495,6 @@ [self _createAccountWithCredentials:credentials]; } -- (void)attemptDeviceRehydrationWithKeyData:(NSData *)keyData - credentials:(MXCredentials *)credentials -{ - [self attemptDeviceRehydrationWithKeyData:keyData - credentials:credentials - retry:YES]; -} - -- (void)attemptDeviceRehydrationWithKeyData:(NSData *)keyData - credentials:(MXCredentials *)credentials - retry:(BOOL)retry -{ - MXLogDebug(@"[MXKAuthenticationViewController] attemptDeviceRehydration: starting device rehydration"); - - if (keyData == nil) - { - MXLogError(@"[MXKAuthenticationViewController] attemptDeviceRehydration: no key provided for device rehydration"); - [self _createAccountWithCredentials:credentials]; - return; - } - - MXRestClient *mxRestClient = [[MXRestClient alloc] initWithCredentials:credentials andOnUnrecognizedCertificateBlock:^BOOL(NSData *certificate) { - return NO; - } andPersistentTokenDataHandler:^(void (^handler)(NSArray *credentials, void (^completion)(BOOL didUpdateCredentials))) { - [[MXKAccountManager sharedManager] readAndWriteCredentials:handler]; - } andUnauthenticatedHandler: nil]; - - MXWeakify(self); - [[MXKAccountManager sharedManager].dehydrationService rehydrateDeviceWithMatrixRestClient:mxRestClient dehydrationKey:keyData success:^(NSString * deviceId) { - MXStrongifyAndReturnIfNil(self); - - if (deviceId) - { - MXLogDebug(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device %@ rehydrated successfully.", deviceId); - credentials.deviceId = deviceId; - } - else - { - MXLogDebug(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device rehydration has been canceled."); - } - - [self _createAccountWithCredentials:credentials]; - } failure:^(NSError *error) { - MXStrongifyAndReturnIfNil(self); - - if (retry) - { - MXLogErrorDetails(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device rehydration failed due to error: Retrying", @{ - @"error": error ?: @"unknown" - }); - [self attemptDeviceRehydrationWithKeyData:keyData credentials:credentials retry:NO]; - return; - } - - MXLogErrorDetails(@"[MXKAuthenticationViewController] attemptDeviceRehydration: device rehydration failed due to error", @{ - @"error": error ?: @"unknown" - }); - - [self _createAccountWithCredentials:credentials]; - }]; -} - - (void)_createAccountWithCredentials:(MXCredentials *)credentials { MXKAccount *account = [[MXKAccount alloc] initWithCredentials:credentials]; diff --git a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.h b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.h index 679e805f8..ecd5b3aad 100644 --- a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.h +++ b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.h @@ -360,13 +360,6 @@ typedef BOOL (^MXKAccountOnCertificateChange)(MXKAccount *mxAccount, NSData *cer #pragma mark - Sync filter -/** - Call this method at an appropriate time to attempt dehydrating to a new backup device - */ -- (void)attemptDeviceDehydrationWithKeyData:(NSData *)keyData - success:(void (^)(void))success - failure:(void (^)(NSError *error))failure; - /** Handle unauthenticated errors from the server triggering hard/soft logouts as appropriate. */ diff --git a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m index 1d0382375..930a63f8a 100644 --- a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m +++ b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m @@ -1715,70 +1715,6 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; }]; } -- (void)attemptDeviceDehydrationWithKeyData:(NSData *)keyData - success:(void (^)(void))success - failure:(void (^)(NSError *error))failure -{ - [self attemptDeviceDehydrationWithKeyData:keyData retry:YES success:success failure:failure]; -} - -- (void)attemptDeviceDehydrationWithKeyData:(NSData *)keyData - retry:(BOOL)retry - success:(void (^)(void))success - failure:(void (^)(NSError *error))failure -{ - if (keyData == nil) - { - MXLogWarning(@"[MXKAccount] attemptDeviceDehydrationWithRetry: no key provided for device dehydration"); - - if (failure) - { - failure(nil); - } - - return; - } - - if (![mxSession.crypto.crossSigning isKindOfClass:[MXLegacyCrossSigning class]]) { - MXLogFailure(@"Device dehydratation is currently only supported by legacy cross signing, add support to all implementations"); - if (failure) - { - failure(nil); - } - return; - } - MXLegacyCrossSigning *crossSigning = (MXLegacyCrossSigning *)mxSession.crypto.crossSigning;; - - MXLogDebug(@"[MXKAccount] attemptDeviceDehydrationWithRetry: starting device dehydration"); - [[MXKAccountManager sharedManager].dehydrationService dehydrateDeviceWithMatrixRestClient:mxRestClient crossSigning:crossSigning dehydrationKey:keyData success:^(NSString *deviceId) { - MXLogDebug(@"[MXKAccount] attemptDeviceDehydrationWithRetry: device successfully dehydrated"); - - if (success) - { - success(); - } - } failure:^(NSError *error) { - if (retry) - { - [self attemptDeviceDehydrationWithKeyData:keyData retry:NO success:success failure:failure]; - MXLogErrorDetails(@"[MXKAccount] attemptDeviceDehydrationWithRetry: device dehydration failed due to error: Retrying.", @{ - @"error": error ?: @"unknown" - }); - } - else - { - MXLogErrorDetails(@"[MXKAccount] attemptDeviceDehydrationWithRetry: device dehydration failed due to error", @{ - @"error": error ?: @"unknown" - }); - - if (failure) - { - failure(error); - } - } - }]; -} - - (void)onMatrixSessionStateChange { // Check if pause has been requested diff --git a/Riot/Modules/MatrixKit/Models/Account/MXKAccountManager.h b/Riot/Modules/MatrixKit/Models/Account/MXKAccountManager.h index 7881832e0..2e21bd9a4 100644 --- a/Riot/Modules/MatrixKit/Models/Account/MXKAccountManager.h +++ b/Riot/Modules/MatrixKit/Models/Account/MXKAccountManager.h @@ -104,8 +104,6 @@ extern NSString *const MXKAccountManagerDataType; */ @property (nonatomic) BOOL isPushAvailable; -@property (nonatomic, readonly) MXDehydrationService *dehydrationService; - /** Retrieve the MXKAccounts manager. diff --git a/Riot/Modules/MatrixKit/Models/Account/MXKAccountManager.m b/Riot/Modules/MatrixKit/Models/Account/MXKAccountManager.m index 6a69ee6a7..c779e5c47 100644 --- a/Riot/Modules/MatrixKit/Models/Account/MXKAccountManager.m +++ b/Riot/Modules/MatrixKit/Models/Account/MXKAccountManager.m @@ -71,7 +71,6 @@ NSString *const MXKAccountManagerDataType = @"org.matrix.kit.MXKAccountManagerDa if (self) { _storeClass = [MXFileStore class]; - _dehydrationService = [MXDehydrationService new]; _savingAccountsEnabled = YES; // Migrate old account file to new format diff --git a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinator.swift b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinator.swift index efb42a35a..27d05afb2 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinator.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinator.swift @@ -34,9 +34,9 @@ final class SecretsRecoveryWithKeyCoordinator: SecretsRecoveryWithKeyCoordinator // MARK: - Setup - init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, cancellable: Bool) { + init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, cancellable: Bool, dehydrationService: DehydrationService?) { - let secretsRecoveryWithKeyViewModel = SecretsRecoveryWithKeyViewModel(recoveryService: recoveryService, recoveryGoal: recoveryGoal) + let secretsRecoveryWithKeyViewModel = SecretsRecoveryWithKeyViewModel(recoveryService: recoveryService, recoveryGoal: recoveryGoal, dehydrationService: dehydrationService) let secretsRecoveryWithKeyViewController = SecretsRecoveryWithKeyViewController.instantiate(with: secretsRecoveryWithKeyViewModel, cancellable: cancellable) self.secretsRecoveryWithKeyViewController = secretsRecoveryWithKeyViewController self.secretsRecoveryWithKeyViewModel = secretsRecoveryWithKeyViewModel diff --git a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewModel.swift b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewModel.swift index f56562f7d..c18222bd0 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewModel.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewModel.swift @@ -24,6 +24,8 @@ final class SecretsRecoveryWithKeyViewModel: SecretsRecoveryWithKeyViewModelType private let recoveryService: MXRecoveryService + private let dehydrationService: DehydrationService? + // MARK: Public let recoveryGoal: SecretsRecoveryGoal @@ -39,8 +41,9 @@ final class SecretsRecoveryWithKeyViewModel: SecretsRecoveryWithKeyViewModelType // MARK: - Setup - init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal) { + init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, dehydrationService: DehydrationService?) { self.recoveryService = recoveryService + self.dehydrationService = dehydrationService self.recoveryGoal = recoveryGoal } @@ -83,6 +86,10 @@ final class SecretsRecoveryWithKeyViewModel: SecretsRecoveryWithKeyViewModelType } self.update(viewState: .loaded) self.coordinatorDelegate?.secretsRecoveryWithKeyViewModelDidRecover(self) + + Task { + await self.dehydrationService?.runDeviceDehydrationFlow(privateKeyData: privateKey) + } }, failure: { [weak self] error in guard let self = self else { return diff --git a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinator.swift b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinator.swift index 02133fcbc..52d34a2eb 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinator.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinator.swift @@ -34,8 +34,8 @@ final class SecretsRecoveryWithPassphraseCoordinator: SecretsRecoveryWithPassphr // MARK: - Setup - init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, cancellable: Bool) { - let secretsRecoveryWithPassphraseViewModel = SecretsRecoveryWithPassphraseViewModel(recoveryService: recoveryService, recoveryGoal: recoveryGoal) + init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, cancellable: Bool, dehydrationService: DehydrationService?) { + let secretsRecoveryWithPassphraseViewModel = SecretsRecoveryWithPassphraseViewModel(recoveryService: recoveryService, recoveryGoal: recoveryGoal, dehydrationService: dehydrationService) let secretsRecoveryWithPassphraseViewController = SecretsRecoveryWithPassphraseViewController.instantiate(with: secretsRecoveryWithPassphraseViewModel, cancellable: cancellable) self.secretsRecoveryWithPassphraseViewController = secretsRecoveryWithPassphraseViewController self.secretsRecoveryWithPassphraseViewModel = secretsRecoveryWithPassphraseViewModel diff --git a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModel.swift b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModel.swift index 71e51e270..dd05f8425 100644 --- a/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModel.swift +++ b/Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewModel.swift @@ -24,6 +24,8 @@ final class SecretsRecoveryWithPassphraseViewModel: SecretsRecoveryWithPassphras private let recoveryService: MXRecoveryService + private let dehydrationService: DehydrationService? + // MARK: Public let recoveryGoal: SecretsRecoveryGoal @@ -39,8 +41,9 @@ final class SecretsRecoveryWithPassphraseViewModel: SecretsRecoveryWithPassphras // MARK: - Setup - init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal) { + init(recoveryService: MXRecoveryService, recoveryGoal: SecretsRecoveryGoal, dehydrationService: DehydrationService?) { self.recoveryService = recoveryService + self.dehydrationService = dehydrationService self.recoveryGoal = recoveryGoal } @@ -103,6 +106,10 @@ final class SecretsRecoveryWithPassphraseViewModel: SecretsRecoveryWithPassphras } self.update(viewState: .loaded) self.coordinatorDelegate?.secretsRecoveryWithPassphraseViewModelDidRecover(self) + + Task { + await self.dehydrationService?.runDeviceDehydrationFlow(privateKeyData: privateKey) + } }, failure: { [weak self] error in guard let self = self else { return diff --git a/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinator.swift b/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinator.swift index 817414956..762c72a73 100644 --- a/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinator.swift +++ b/Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinator.swift @@ -85,14 +85,28 @@ final class SecretsRecoveryCoordinator: SecretsRecoveryCoordinatorType { // MARK: - Private + private var dehydrationService: DehydrationService? { + if self.session.vc_homeserverConfiguration().encryption.deviceDehydrationEnabled { + return self.session.crypto.dehydrationService + } + + return nil + } + private func createRecoverFromKeyCoordinator() -> SecretsRecoveryWithKeyCoordinator { - let coordinator = SecretsRecoveryWithKeyCoordinator(recoveryService: self.session.crypto.recoveryService, recoveryGoal: self.recoveryGoal, cancellable: self.cancellable) + let coordinator = SecretsRecoveryWithKeyCoordinator(recoveryService: self.session.crypto.recoveryService, + recoveryGoal: self.recoveryGoal, + cancellable: self.cancellable, + dehydrationService: dehydrationService) coordinator.delegate = self return coordinator } private func createRecoverFromPassphraseCoordinator() -> SecretsRecoveryWithPassphraseCoordinator { - let coordinator = SecretsRecoveryWithPassphraseCoordinator(recoveryService: self.session.crypto.recoveryService, recoveryGoal: self.recoveryGoal, cancellable: self.cancellable) + let coordinator = SecretsRecoveryWithPassphraseCoordinator(recoveryService: self.session.crypto.recoveryService, + recoveryGoal: self.recoveryGoal, + cancellable: self.cancellable, + dehydrationService: dehydrationService) coordinator.delegate = self return coordinator } diff --git a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinator.swift b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinator.swift index 58ebf79ba..9dc055e49 100644 --- a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinator.swift +++ b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyCoordinator.swift @@ -42,8 +42,13 @@ final class SecretsSetupRecoveryKeyCoordinator: SecretsSetupRecoveryKeyCoordinat passphrase: String?, passphraseOnly: Bool, allowOverwrite: Bool = false, - cancellable: Bool) { - let secretsSetupRecoveryKeyViewModel = SecretsSetupRecoveryKeyViewModel(recoveryService: recoveryService, passphrase: passphrase, passphraseOnly: passphraseOnly, allowOverwrite: allowOverwrite) + cancellable: Bool, + dehydrationService: DehydrationService?) { + let secretsSetupRecoveryKeyViewModel = SecretsSetupRecoveryKeyViewModel(recoveryService: recoveryService, + passphrase: passphrase, + passphraseOnly: passphraseOnly, + allowOverwrite: allowOverwrite, + dehydrationService: dehydrationService) let secretsSetupRecoveryKeyViewController = SecretsSetupRecoveryKeyViewController.instantiate(with: secretsSetupRecoveryKeyViewModel, cancellable: cancellable) self.secretsSetupRecoveryKeyViewModel = secretsSetupRecoveryKeyViewModel self.secretsSetupRecoveryKeyViewController = secretsSetupRecoveryKeyViewController diff --git a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift index af85b7eb7..c91cb6787 100644 --- a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift +++ b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift @@ -28,6 +28,7 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy private let passphrase: String? private let passphraseOnly: Bool private let allowOverwrite: Bool + private let dehydrationService: DehydrationService? // MARK: Public @@ -36,11 +37,12 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy // MARK: - Setup - init(recoveryService: MXRecoveryService, passphrase: String?, passphraseOnly: Bool, allowOverwrite: Bool = false) { + init(recoveryService: MXRecoveryService, passphrase: String?, passphraseOnly: Bool, allowOverwrite: Bool = false, dehydrationService: DehydrationService?) { self.recoveryService = recoveryService self.passphrase = passphrase self.passphraseOnly = passphraseOnly self.allowOverwrite = allowOverwrite + self.dehydrationService = dehydrationService } // MARK: - Public @@ -76,6 +78,10 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy self.recoveryService.createRecovery(forSecrets: nil, withPassphrase: self.passphrase, createServicesBackups: true, success: { secretStorageKeyCreationInfo in self.update(viewState: .recoveryCreated(secretStorageKeyCreationInfo.recoveryKey)) + + Task { + await self.dehydrationService?.runDeviceDehydrationFlow(privateKeyData: secretStorageKeyCreationInfo.privateKey) + } }, failure: { error in self.update(viewState: .error(error)) }) diff --git a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift index 0cb633945..9752e3609 100644 --- a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift +++ b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift @@ -104,8 +104,21 @@ final class SecureBackupSetupCoordinator: SecureBackupSetupCoordinatorType { return introViewController } + private var dehydrationService: DehydrationService? { + if self.session.vc_homeserverConfiguration().encryption.deviceDehydrationEnabled { + return self.session.crypto.dehydrationService + } + + return nil + } + private func showSetupKey(passphraseOnly: Bool, passphrase: String? = nil) { - let coordinator = SecretsSetupRecoveryKeyCoordinator(recoveryService: self.recoveryService, passphrase: passphrase, passphraseOnly: passphraseOnly, allowOverwrite: allowOverwrite, cancellable: self.cancellable) + let coordinator = SecretsSetupRecoveryKeyCoordinator(recoveryService: self.recoveryService, + passphrase: passphrase, + passphraseOnly: passphraseOnly, + allowOverwrite: allowOverwrite, + cancellable: self.cancellable, + dehydrationService: dehydrationService) coordinator.delegate = self coordinator.start() From d4d18d4e685fbe77883f421d77e6a669e901fa77 Mon Sep 17 00:00:00 2001 From: random Date: Fri, 16 Jun 2023 10:18:13 +0000 Subject: [PATCH 08/82] Translated using Weblate (Italian) Currently translated at 100.0% (2411 of 2411 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 37 ++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 81f489410..d3c3cf5c5 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -274,7 +274,7 @@ "room_title_members" = "%@ membri"; "room_title_one_member" = "1 membro"; // Room Preview -"room_preview_invitation_format" = "Sei stato invitato ad entrare in questa stanza da %@"; +"room_preview_invitation_format" = "Sei stato/a invitato/a ad entrare in questa stanza da %@"; "room_preview_subtitle" = "Questa è l'anteprima della stanza. Le interazioni sono disabilitate."; "room_preview_unlinked_email_warning" = "Questo invito è stato spedito a %@, che non è associato a questo account. Puoi aggiungere questa email al tuo account o provare ad accedere con un account differente."; "room_preview_try_join_an_unknown_room" = "Stai provando ad accedere a %@. Desideri entrare per partecipare alla discussione?"; @@ -457,7 +457,7 @@ "group_home_multi_members_format" = "%tu membri"; "group_home_one_room_format" = "1 stanza"; "group_home_multi_rooms_format" = "%tu stanze"; -"group_invitation_format" = "%@ ti ha invitato ad unirti a questa comunità"; +"group_invitation_format" = "%@ ti ha invitato/a ad unirti a questa comunità"; // Group participants "group_participants_add_participant" = "Aggiungi membri"; "group_participants_leave_prompt_title" = "Lascia la stanza"; @@ -649,7 +649,7 @@ "sign_out_key_backup_in_progress_alert_cancel_action" = "Attendo"; "close" = "Chiudi"; "auth_forgot_password_error_no_configured_identity_server" = "Non è stato configurato alcun server d'identità: aggiungine uno per ripristinare la password dell'account Matrix."; -"auth_softlogout_signed_out" = "Sei uscito"; +"auth_softlogout_signed_out" = "Sei disconnesso/a"; "auth_softlogout_sign_in" = "Accedi"; "auth_softlogout_reason" = "L'amministratore dell'Home Server (%1$@) ti ha disconnesso dal tuo account %2$@ (%3$@)."; "auth_softlogout_recover_encryption_keys" = "Accedi per recuperare le chiavi crittografiche archiviate su questo dispositivo. Le chiavi ti servono per poter leggere i tuoi messaggi cifrati su altri dispositivi."; @@ -1164,7 +1164,7 @@ "biometrics_cant_unlocked_alert_message_login" = "Riaccedi"; "biometrics_cant_unlocked_alert_message_retry" = "Riprova"; "device_verification_self_verify_wait_recover_secrets_checking_availability" = "Controllo di altre possibilità di verifica..."; -"joined" = "Entrato"; +"joined" = "Entrato/a"; "switch" = "Cambia"; "more" = "Altro"; "pin_protection_choose_pin_welcome_after_login" = "Bentornato/a."; @@ -1832,7 +1832,7 @@ "room_error_cannot_load_timeline" = "Caricamento storico dei messaggi fallito"; "room_error_timeline_event_not_found_title" = "Caricamento della posizione nello storico fallito"; "room_error_timeline_event_not_found" = "L'applicazione ha cercato di caricare un punto specifico dello storico dei messaggi in questo canale, ma non è riuscita a trovarlo"; -"room_left" = "Sei uscito dalla stanza"; +"room_left" = "Sei uscito/a dalla stanza"; "room_no_power_to_create_conference_call" = "Hai bisogno del permesso per invitare a iniziare una conferenza in questo canale"; "room_no_conference_call_in_encrypted_rooms" = "Le chiamate in conferenza non sono supportate nei canali criptati"; // Reply to message @@ -1906,7 +1906,7 @@ // Language picker "language_picker_title" = "Scegli una lingua"; "language_picker_default_language" = "Predefinito (%@)"; -"notice_room_invite" = "%@ invitato %@"; +"notice_room_invite" = "%@ ha invitato %@"; "notice_room_third_party_invite" = "%@ ha invitato %@ a unirsi al canale"; "notice_room_third_party_registered_invite" = "%@ ha accettato l'invito per %@"; "notice_room_third_party_revoked_invite" = "%@ ha ritirato l'invito per %@ a unirsi al canale"; @@ -1944,7 +1944,7 @@ "login" = "Entra"; "create_account" = "Crea utente"; "membership_invite" = "Invitati"; -"membership_leave" = "Uscito"; +"membership_leave" = "Uscito/a"; "membership_ban" = "Espulso"; "num_members_one" = "%@ utente"; "num_members_other" = "%@ utenti"; @@ -1979,7 +1979,7 @@ "notification_settings_contain_my_user_name" = "Notifica con un suono i messaggi che contengono il mio nome utente"; "notification_settings_contain_my_display_name" = "Notifica con un suono i messaggi che contengono il mio nome completo"; "notification_settings_just_sent_to_me" = "Notifica con un suono i messaggi inviati solo a me"; -"notification_settings_invite_to_a_new_room" = "Notifica quando sono invitato in un nuovo canale"; +"notification_settings_invite_to_a_new_room" = "Avvisami quando sono invitato/a in una nuova stanza"; "notification_settings_people_join_leave_rooms" = "Notifica quando gli utenti entrano o escono dai canali"; "notification_settings_receive_a_call" = "Notifica quando ricevo una chiamata"; "notification_settings_suppress_from_bots" = "Sopprimi le notifiche dai bot"; @@ -2009,12 +2009,12 @@ "notice_encryption_enabled_unknown_algorithm" = "%1$@ ha attivato la crittografia end-to-end (algoritmo %2$@ non riconosciuto)."; // Notice Events with "You" "notice_room_invite_by_you" = "Hai invitato %@"; -"notice_room_invite_you" = "%@ ti ha invitato"; +"notice_room_invite_you" = "%@ ti ha invitato/a"; "notice_room_third_party_invite_by_you" = "Hai mandato un invito a %@ a unirsi alla stanza"; "notice_room_third_party_registered_invite_by_you" = "Hai accettato l'invito per %@"; "notice_room_third_party_revoked_invite_by_you" = "Hai revocato l'invito per %@ a unirsi alla stanza"; -"notice_room_join_by_you" = "Sei entrato"; -"notice_room_leave_by_you" = "Sei uscito"; +"notice_room_join_by_you" = "Sei entrato/a"; +"notice_room_leave_by_you" = "Sei uscito/a"; "notice_room_reject_by_you" = "Hai rifiutato l'invito"; "notice_room_kick_by_you" = "Hai rimosso %@"; "notice_room_unban_by_you" = "Hai riammesso %@"; @@ -2049,7 +2049,7 @@ "notice_room_join_rule_public" = "%@ ha reso la stanza pubblica."; "notice_room_join_rule_public_by_you" = "Hai reso la stanza pubblica."; "notice_room_name_removed_for_dm" = "%@ ha rimosso il nome"; -"notice_room_created_for_dm" = "%@ è entrato."; +"notice_room_created_for_dm" = "%@ è entrato/a."; "notice_room_join_rule_invite_for_dm" = "%@ l'ha resa solo su invito."; "notice_room_join_rule_invite_by_you_for_dm" = "L'hai resa solo su invito."; "notice_room_join_rule_public_for_dm" = "%@ l'ha resa pubblica."; @@ -2059,7 +2059,7 @@ "notice_room_history_visible_to_members_for_dm" = "%@ ha reso visibili i messaggi futuri a tutti i membri della stanza."; "notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ ha reso visibili i messaggi futuri a chiunque, dal momento dell'invito."; "notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ ha reso visibili i messaggi futuri a chiunque, dal momento dell'entrata."; -"room_left_for_dm" = "Sei uscito"; +"room_left_for_dm" = "Sei uscito/a"; "notice_room_third_party_invite_for_dm" = "%@ ha invitato %@"; "notice_room_third_party_revoked_invite_for_dm" = "%@ ha revocato l'invito per %@"; "notice_room_name_changed_for_dm" = "%@ ha cambiato il nome in %@."; @@ -2067,7 +2067,7 @@ "notice_room_third_party_revoked_invite_by_you_for_dm" = "Hai revocato l'invito per %@"; "notice_room_name_changed_by_you_for_dm" = "Hai cambiato il nome in %@."; "notice_room_name_removed_by_you_for_dm" = "Hai rimosso il nome"; -"notice_room_created_by_you_for_dm" = "Sei entrato."; +"notice_room_created_by_you_for_dm" = "Sei entrato/a."; "notice_room_history_visible_to_members_by_you_for_dm" = "Hai reso visibili i messaggi futuri a tutti i membri della stanza."; "notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Hai reso visibili i messaggi futuri a chiunque, dal momento dell'invito."; "notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Hai reso visibili i messaggi futuri a chiunque, dal momento dell'entrata."; @@ -2096,7 +2096,7 @@ "attachment_small_with_resolution" = "Piccolo %@ (~%@)"; "attachment_size_prompt_message" = "Puoi disattivarlo nelle impostazioni."; "attachment_size_prompt_title" = "Conferma dimensione da inviare"; -"room_displayname_all_other_members_left" = "%@ (Uscito)"; +"room_displayname_all_other_members_left" = "%@ (Uscito/a)"; "attachment_unsupported_preview_message" = "Questo tipo di file non è supportato."; "attachment_unsupported_preview_title" = "Anteprima non disponibile"; "message_reply_to_sender_sent_their_location" = "ha condiviso la sua posizione."; @@ -2273,7 +2273,7 @@ "location_sharing_live_list_item_last_update_invalid" = "Ultimo aggiornamento sconosciuto"; "location_sharing_live_list_item_last_update" = "Aggiornato %@ fa"; "location_sharing_live_list_item_sharing_expired" = "Condivisione scaduta"; -"location_sharing_live_list_item_time_left" = "%@ è uscito"; +"location_sharing_live_list_item_time_left" = "%@ è uscito/a"; "location_sharing_live_viewer_title" = "Posizione"; "location_sharing_live_map_callout_title" = "Condividi posizione"; "bug_report_logs_description" = "Per diagnosticare i problemi, i registri di questo client verranno inviati con questo rapporto di errore. Se preferisci inviare solo il testo soprastante, deseleziona:"; @@ -2737,3 +2737,8 @@ "room_command_change_display_name_description" = "Cambia il tuo nome visualizzato"; "notice_display_name_changed_to" = "%@ ha cambiato il suo nome visualizzato in %@"; "poll_timeline_loading" = "Caricamento..."; +"room_creation_user_not_found_prompt_title" = "Conferma"; +"room_creation_user_not_found_prompt_invite_action" = "Inizia il messaggio lo stesso"; +"room_participants_invite_anyway" = "Invita lo stesso"; +"room_participants_invite_unknown_participant_prompt_to_msg" = "Impossibile trovare profili con questo ID Matrix. Vuoi davvero invitare %@ in %@?"; +"room_creation_user_not_found_prompt_message" = "Impossibile trovare profili con questo ID Matrix. Vuoi comunque iniziare un messaggio diretto?"; From 436b545cfaf2b4aad7aaf1e5d223fb5a94882d16 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Fri, 30 Jun 2023 18:54:59 +0000 Subject: [PATCH 09/82] Translated using Weblate (Swedish) Currently translated at 99.2% (2389 of 2406 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sv/ --- Riot/Assets/sv.lproj/Vector.strings | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Riot/Assets/sv.lproj/Vector.strings b/Riot/Assets/sv.lproj/Vector.strings index bbb4b12c5..17ad24b8d 100644 --- a/Riot/Assets/sv.lproj/Vector.strings +++ b/Riot/Assets/sv.lproj/Vector.strings @@ -2676,3 +2676,9 @@ "room_command_change_room_topic_description" = "Sätter rummets ämne"; "room_command_discard_session_description" = "Tvingar den aktuella utgående gruppsessionen i ett krypterat rum att kasseras"; "room_command_error_unknown_command" = "Ogiltigt eller obehandlat kommando"; + +// Legacy to Rust security upgrade + +"key_verification_self_verify_security_upgrade_alert_title" = "App uppdaterad"; +"key_verification_self_verify_security_upgrade_alert_message" = "Säker meddelandehantering har förbättrats med den senaste uppdateringen. Vänligen verifiera din enhet igen."; +"device_verification_self_verify_open_on_other_device_title" = "Öppna %@ på din andra enhet"; From 58956b420e8082a5e3a527557adfe9b82e741be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 5 Jul 2023 05:33:01 +0000 Subject: [PATCH 10/82] Translated using Weblate (Estonian) Currently translated at 100.0% (2411 of 2411 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ --- Riot/Assets/et.lproj/Vector.strings | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings index 21f95be98..0cad547d7 100644 --- a/Riot/Assets/et.lproj/Vector.strings +++ b/Riot/Assets/et.lproj/Vector.strings @@ -2709,3 +2709,8 @@ "room_command_change_display_name_description" = "Muudab sinu kuvatavat nime"; "notice_display_name_changed_to" = "%@ muutis oma kuvatavaks nimeks %@"; "poll_timeline_loading" = "Laadin..."; +"room_creation_user_not_found_prompt_title" = "Kinnitus"; +"room_creation_user_not_found_prompt_invite_action" = "Ikkagi alusta vestlust"; +"room_creation_user_not_found_prompt_message" = "Sellele Matrix'i kasutajatunnuse profiili ei leidu. Kas sa ikkagi tahaksid temaga vestlust alustada?"; +"room_participants_invite_unknown_participant_prompt_to_msg" = "Sellele Matrix'i kasutajatunnuse profiili ei leidu. Kas sa ikkagi tahaksid saata kutset kasutajale %@ jututuppa %@?"; +"room_participants_invite_anyway" = "Kutsu siiski"; From 6669c9f7d50e16ec07a20f5d0753b9de4917c48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D1=8F?= Date: Sun, 2 Jul 2023 17:51:17 +0000 Subject: [PATCH 11/82] Translated using Weblate (Russian) Currently translated at 85.8% (2066 of 2406 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ --- Riot/Assets/ru.lproj/Vector.strings | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/Riot/Assets/ru.lproj/Vector.strings b/Riot/Assets/ru.lproj/Vector.strings index 2f016c5fd..3e96f0ad3 100644 --- a/Riot/Assets/ru.lproj/Vector.strings +++ b/Riot/Assets/ru.lproj/Vector.strings @@ -2265,3 +2265,91 @@ // Room Access Settings "room_access_settings_screen_nav_title" = "Доступ к комнате"; "spaces_coming_soon_detail" = "Эта функция еще не реализована здесь, но она в разработке. На данный момент вы можете сделать это с помощью %@ на своем компьютере."; + +// Unverified sessions +"key_verification_alert_title" = "У вас есть не подтвержденные сессии"; +"key_verification_scan_qr_code_title" = "Сканируйте QR-код"; +"home_context_menu_favourite" = "Любимые"; +"spaces_add_space_title" = "Создать пространство"; +"room_details_promote_room_title" = "Продвигать комнату"; +"room_suggestion_settings_screen_title" = "Сделать комнату предлагаемой в пространстве"; +"analytics_prompt_terms_link_new_user" = "здесь"; +"create_room_suggest_room" = "Предложения для пользователей пространства"; +"room_info_back_button_title" = "Информация о комнате"; +"home_context_menu_make_dm" = "Перейти к людям"; +"create_room_processing" = "Создание комнаты"; +"home_context_menu_unmute" = "Снять заглушку"; + +// MARK: - Room invite + +"room_invite_to_space_option_title" = "К %@"; +"analytics_prompt_yes" = "Да, все классно"; +"create_room_section_footer_type_private" = "Только приглашенные люди могут искать и вступать."; +"home_context_menu_make_room" = "Перейти к комнатам"; +"create_room_suggest_room_footer" = "Предлагаемые комнаты выдвинуты для участников как лучший выбор для вступления."; +"home_context_menu_notifications" = "Уведомления"; +"analytics_prompt_terms_link_upgrade" = "здесь"; +"device_verification_self_verify_open_on_other_device_title" = "Откройте %@ на другом вашем устройстве"; +"create_room_section_footer_type_public" = "Только приглашенные люди могут искать и вступать, не только люди из пространства."; +"home_context_menu_unfavourite" = "Удалить из любимых"; +"home_context_menu_normal_priority" = "Нормальный приоритет"; +"home_context_menu_mute" = "Заглушить"; +"room_access_settings_screen_upgrade_alert_upgrading" = "Улучшение комнаты"; +"room_access_settings_screen_upgrade_alert_upgrade_button" = "Улучшить"; +"key_backup_recover_from_private_key_progress" = "%@%% закончено"; +"room_access_settings_screen_upgrade_alert_title" = "Улучшить комнату"; +"share_extension_low_quality_video_message" = "Отправьте в %@ для лучшего качества или отправьте в пониженном качестве."; +"settings_acceptable_use" = "Принимаемые политики пользования"; +"room_suggestion_settings_screen_message" = "Предлагаемые комнаты рекламируются среди участников пространства как подходящие для присоединения."; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_new_user */ +"analytics_prompt_terms_new_user" = "Вы можете прочитать все наши условия %@."; +"key_verification_alert_body" = "Перепроверьте надежность сохранности аккаунта."; + +// MARK: - Launch loading + +"launch_loading_generic" = "Синхронизация ваших разговоров"; +"home_context_menu_mark_as_unread" = "Пометить как непрочитанное"; + +// MARK: Sign out warning + +"sign_out" = "Выйти"; +"room_command_unban_user_description" = "Разблокирует пользователя с указанным ID"; +"device_verification_self_verify_open_on_other_device_information" = "Вам нужно подтвердить эту сессию для того, чтобы прочитать защищенную историю сообщений\n\nОткройте Element на одном из ваших прочих устройств и действуйте по инструкции."; +"room_command_change_room_topic_description" = "Указывает тему комнаты"; +"room_access_settings_screen_upgrade_required" = "Требуется улучшение"; +"key_verification_scan_qr_code_information_other_user" = "Наведите камеру на QR-код, отображенном на вашем устройстве для подтверждения ваших сессий"; + +// Room suggestion Settings +"room_suggestion_settings_screen_nav_title" = "Предложить комнату"; +/* Note: The placeholder is for the contents of analytics_prompt_terms_link_upgrade */ +"analytics_prompt_terms_upgrade" = "Прочитать все наши условия %@. Они подходят?"; +"key_verification_scan_qr_code_information_other_session" = "Наведите камеру на QR-код, отображенном на другом вашем устройстве для подтверждения вашей сессии"; +"key_verification_scan_qr_code_information_other_device" = "Наведите камеру на QR-код, отображенном на другом вашем устройстве для подтверждения этой сессии"; +"create_room_type_restricted" = "Участники пространства"; +"home_context_menu_low_priority" = "Низкий приоритет"; +"share_invite_link_space_text" = "Хэй, вступай в это пространство на %@"; +"create_room_promotion_header" = "ПРОДВИЖЕНИЕ"; +"create_room_show_in_directory_footer" = "Это может помочь людям искать и вступать."; +"space_invite_not_enough_permission" = "У вас нет разрешения для приглашения людей в это пространство"; + +// Room commands descriptions +"room_command_change_display_name_description" = "Изменяет отображаемое имя пользователя"; +"room_command_emote_description" = "Отображает действие"; +"room_command_join_room_description" = "Присоединяться к комнате с указанным адресом"; +"room_command_part_room_description" = "Покинуть комнату"; +"room_command_invite_user_description" = "Добавляет пользователя с указанным ID в текущую комнату"; +"room_command_kick_user_description" = "Удаляет пользователя с указанным ID из этой комнаты"; +"room_command_ban_user_description" = "Блокирует пользователя с указанным ID"; +"room_command_set_user_power_level_description" = "Назначает уровень силы пользователя"; +"room_command_reset_user_power_level_description" = "Разжалует пользователя с указанным ID"; +"room_command_discard_session_description" = "Принудительно отбрасывает текущий исходящий групповой сеанс в зашифрованной комнате"; +"room_command_error_unknown_command" = "Некорректная или необработанная команда"; + +// Legacy to Rust security upgrade + +"key_verification_self_verify_security_upgrade_alert_title" = "Приложение обновлено"; +"key_verification_self_verify_security_upgrade_alert_message" = "Безопасное общение может быть улучшено с новым обновлением. Пожалуйста, проверьте ваше устройство."; +"device_verification_self_verify_wait_recover_secrets_additional_help" = "Нет доступа к существующей %@ сессии?"; +"key_verification_scan_qr_code_information_new_session" = "Наведите камеру на QR-код, отображенном на другом вашем устройстве для подтверждения вашей новой сессии"; +"create_room_section_footer_type_restricted" = "Все в пространстве могут искать и вступать."; +"launch_loading_delay_warning" = "Это может занимать больше времени.\nСпасибо за терпение."; From 3441ed62c62fc06eb7e9484314c31dc7ce11a9c8 Mon Sep 17 00:00:00 2001 From: Vri Date: Fri, 7 Jul 2023 12:00:23 +0000 Subject: [PATCH 12/82] Translated using Weblate (German) Currently translated at 100.0% (2411 of 2411 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ --- Riot/Assets/de.lproj/Vector.strings | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 035279b74..60aa50e1f 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -2771,3 +2771,8 @@ "notice_display_name_changed_to" = "%@ hat den Anzeigenamen zu %@ geändert"; "poll_timeline_loading" = "Lade …"; "room_command_discard_session_description" = "Erzwingt das Verferfen der aktuell ausgehende Gruppensitzung in einem verschlüsseltem Raum"; +"room_creation_user_not_found_prompt_title" = "Bestätigung"; +"room_creation_user_not_found_prompt_message" = "Wir konnten kein Profil für diese Matrix-ID finden. Möchtest du dennoch eine Direktnachricht beginnen?"; +"room_creation_user_not_found_prompt_invite_action" = "Dennoch DM beginnen"; +"room_participants_invite_unknown_participant_prompt_to_msg" = "Wir konnten kein Profil für diese Matrix-ID finden. Möchtest du wirklich %@ nach %@ einladen?"; +"room_participants_invite_anyway" = "Dennoch einladen"; From 5b75dd997bbda2fd4827ecad61da9096603c082e Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Mon, 24 Jul 2023 19:31:46 +0000 Subject: [PATCH 13/82] Translated using Weblate (Swedish) Currently translated at 99.4% (2392 of 2406 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sv/ --- Riot/Assets/sv.lproj/Vector.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Riot/Assets/sv.lproj/Vector.strings b/Riot/Assets/sv.lproj/Vector.strings index 17ad24b8d..a4bb2fe70 100644 --- a/Riot/Assets/sv.lproj/Vector.strings +++ b/Riot/Assets/sv.lproj/Vector.strings @@ -2682,3 +2682,6 @@ "key_verification_self_verify_security_upgrade_alert_title" = "App uppdaterad"; "key_verification_self_verify_security_upgrade_alert_message" = "Säker meddelandehantering har förbättrats med den senaste uppdateringen. Vänligen verifiera din enhet igen."; "device_verification_self_verify_open_on_other_device_title" = "Öppna %@ på din andra enhet"; +"device_verification_self_verify_wait_recover_secrets_additional_help" = "Kan du inte komma åt en befintlig %@-session?"; +"key_verification_scan_qr_code_title" = "Skanna QR-kod"; +"device_verification_self_verify_open_on_other_device_information" = "Du behöver verifiera den här sessionen för att kunna läsa din säkra meddelandehistorik.\n\nÖppna Element på en av dina andra enheter och följ instruktionerna."; From 913863124b07ebf3fd3b0d02fd15997c2012f137 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Mon, 24 Jul 2023 19:32:18 +0000 Subject: [PATCH 14/82] Translated using Weblate (Swedish) Currently translated at 100.0% (2406 of 2406 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sv/ --- Riot/Assets/sv.lproj/Vector.strings | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Riot/Assets/sv.lproj/Vector.strings b/Riot/Assets/sv.lproj/Vector.strings index a4bb2fe70..48d13aea8 100644 --- a/Riot/Assets/sv.lproj/Vector.strings +++ b/Riot/Assets/sv.lproj/Vector.strings @@ -2685,3 +2685,22 @@ "device_verification_self_verify_wait_recover_secrets_additional_help" = "Kan du inte komma åt en befintlig %@-session?"; "key_verification_scan_qr_code_title" = "Skanna QR-kod"; "device_verification_self_verify_open_on_other_device_information" = "Du behöver verifiera den här sessionen för att kunna läsa din säkra meddelandehistorik.\n\nÖppna Element på en av dina andra enheter och följ instruktionerna."; +"key_verification_scan_qr_code_information_other_user" = "Rikta kameran mot QR-koden som visas på deras enhet för att verifiera deras session"; +"room_waiting_other_participants_title" = "Väntar på att användare ska gå med i %@"; +"pill_message_from" = "Meddelande från %@"; +"pill_message_in" = "Meddelande i %@"; +"notice_display_name_changed_to" = "%@ bytte sitt visningsnamn till %@"; + +// MARK: - Launch loading + +"launch_loading_generic" = "Synkar dina konversationer"; +"launch_loading_delay_warning" = "Detta kan ta lite längre tid.\nTack för ditt tålamod."; +"key_verification_scan_qr_code_information_other_device" = "Rikta kameran mot QR-koden som visas på din andra enhet för att verifiera den här sessionen"; +"room_waiting_other_participants_message" = "När inbjudna användare har gått med i %@ kommer du att kunna chatta och rummet kommer att totalsträckskrypteras"; +"poll_timeline_loading" = "Laddar …"; + +// Pills +"pill_room_fallback_display_name" = "Utrymme/rum"; +"pill_message" = "Meddelande"; +"key_verification_scan_qr_code_information_new_session" = "Rikta kameran mot QR-koden som visas på din andra enhet för att verifiera din nya session"; +"key_verification_scan_qr_code_information_other_session" = "Rikta kameran mot QR-koden som visas på din andra enhet för att verifiera din session"; From 9be1bdc7abc0bbaa12293a5e5739e83b7e7f1bb3 Mon Sep 17 00:00:00 2001 From: Kim Brose Date: Fri, 28 Jul 2023 11:24:29 +0000 Subject: [PATCH 15/82] Translated using Weblate (German) Currently translated at 100.0% (2411 of 2411 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ --- Riot/Assets/de.lproj/Vector.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 60aa50e1f..7b91c3a9f 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -406,8 +406,8 @@ "settings_ui_language" = "Sprache"; // Events formatter "event_formatter_member_updates" = "%tu Änderungen der Mitgliedschaft"; -"contacts_user_directory_section" = "NUTZER VERZEICHNIS"; -"contacts_user_directory_offline_section" = "NUTZER VERZEICHNIS (offline)"; +"contacts_user_directory_section" = "NUTZERVERZEICHNIS"; +"contacts_user_directory_offline_section" = "NUTZERVERZEICHNIS (offline)"; "auth_home_server_placeholder" = "URL (z.B. https://matrix.org)"; "auth_identity_server_placeholder" = "URL (z. B. https://vector.im)"; "room_ongoing_conference_call_close" = "Schließen"; From e816978d20b9bba6ea7ffa92d21ab549e289c395 Mon Sep 17 00:00:00 2001 From: Vri Date: Tue, 1 Aug 2023 13:05:20 +0000 Subject: [PATCH 16/82] Translated using Weblate (German) Currently translated at 100.0% (2411 of 2411 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ --- Riot/Assets/de.lproj/Vector.strings | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 7b91c3a9f..ae6ee3d05 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -2396,12 +2396,12 @@ "room_suggestion_settings_screen_nav_title" = "Raum vorschlagen"; "room_access_space_chooser_other_spaces_section_info" = "Diese sind vermutlich Dinge, zu denen andere Admins von %@ gehören."; "room_access_space_chooser_other_spaces_section" = "Andere Spaces oder Räume"; -"room_access_settings_screen_setting_room_access" = "Lege Raumzugriff fest"; -"room_access_settings_screen_upgrade_alert_upgrading" = "Raum upgraden"; +"room_access_settings_screen_setting_room_access" = "Raumzutritt festlegen"; +"room_access_settings_screen_upgrade_alert_upgrading" = "Raum aktualisieren"; "room_access_settings_screen_upgrade_alert_upgrade_button" = "Aktualisieren"; "room_access_settings_screen_upgrade_alert_auto_invite_switch" = "Mitglieder automatisch zu neuem Raum einladen"; -"room_access_settings_screen_upgrade_alert_message" = "Jeder in %@ kann diesen Raum finden und ihm beitreten - jeden manuell einzuladen ist nicht nötig. Du kannst diese Einstellung jederzeit ändern."; -"room_access_settings_screen_upgrade_alert_title" = "Raum upgraden"; +"room_access_settings_screen_upgrade_alert_message" = "Jeder in %@ kann diesen Raum finden und ihm beitreten – jeden manuell einzuladen ist nicht nötig. Du kannst diese Einstellung jederzeit ändern."; +"room_access_settings_screen_upgrade_alert_title" = "Raum aktualisieren"; "room_access_settings_screen_edit_spaces" = "Spaces bearbeiten"; "room_access_settings_screen_upgrade_required" = "Upgrade erforderlich"; "room_access_settings_screen_message" = "Lege fest, wer %@ finden und beitreten kann."; @@ -2461,13 +2461,13 @@ "threads_discourage_information_1" = "Dein Heimserver unterstützt aktuell keine Threads, weshalb diese Funktion unzuverlässig sein könnte. Manche Thread-Nachrichten könnten nicht zuverlässig verfügbar sein. "; "all_chats_nothing_found_placeholder_title" = "Nichts gefunden."; "spaces_create_subspace_title" = "Sub-Space erstellen"; -"room_access_settings_screen_upgrade_alert_note" = "Bitte beachte, dass das Upgrade eine neue Version dieses Raums erstellt. Alle aktuellen Nachrichten bleiben in diesem archivierten Raum."; +"room_access_settings_screen_upgrade_alert_note" = "Bitte beachte, dass die Aktualisierung eine neue Version dieses Raums erstellt. Alle aktuellen Nachrichten bleiben in diesem archivierten Raum."; "invite_to" = "Zu %@ einladen"; "all_chats_empty_unreads_placeholder_message" = "Hier werden deine ungelesenen Nachrichten erscheinen, wenn du welche hast."; "all_chats_edit_layout_show_recents" = "Historie anzeigen"; "all_chats_empty_list_placeholder_title" = "Du bist auf dem neuesten Stand."; "spaces_explore_rooms_format" = "%@ erkunden"; -"room_access_settings_screen_upgrade_alert_message_no_param" = "Jeder in einem übergeordneten Space kann diesen Raum finden und ihm beitreten - jeden manuell einzuladen ist nicht nötig. Du kannst diese Einstellung jederzeit ändern."; +"room_access_settings_screen_upgrade_alert_message_no_param" = "Jeder in einem übergeordneten Space kann diesen Raum finden und ihm beitreten – jeden manuell einzuladen ist nicht nötig. Du kannst diese Einstellung jederzeit ändern."; "room_access_settings_screen_public_message" = "Sichtbar und zugänglich für jeden."; "room_access_settings_screen_restricted_message" = "Sichtbar und betretbar für jeden Nutzer in einem Space.\nDu wählst, für welche Spaces dies gilt."; "room_access_settings_screen_private_message" = "Nur sichtbar und betretbar für eingeladene Personen."; From fc8d0cb01fe376222b4571a0020b8dd7d687e168 Mon Sep 17 00:00:00 2001 From: Victor Grousset Date: Fri, 4 Aug 2023 03:04:48 +0000 Subject: [PATCH 17/82] Translated using Weblate (Esperanto) Currently translated at 62.1% (1499 of 2411 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/eo/ --- Riot/Assets/eo.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/eo.lproj/Vector.strings b/Riot/Assets/eo.lproj/Vector.strings index 2e91114ed..a09965843 100644 --- a/Riot/Assets/eo.lproj/Vector.strings +++ b/Riot/Assets/eo.lproj/Vector.strings @@ -1968,7 +1968,7 @@ "notice_encrypted_message" = "Ĉifrita mesaĝo"; "notice_room_related_groups" = "Grupoj rilataj al ĉi tiu ĉambro estas: %@"; "notice_room_aliases_for_dm" = "La kromnomoj estas: %@"; -"notice_room_aliases" = "Kromnomoj de la ĉamrbo estas: %@"; +"notice_room_aliases" = "Kromnomoj de la ĉambro estas: %@"; "notice_room_power_level_event_requirement" = "La minimumaj povniveloj rilataj al okazoj estas:"; "notice_room_power_level_acting_requirement" = "La minimuma povnivelo, kiun uzanto bezonas antaŭ agi, estas:"; "notice_room_power_level_intro_for_dm" = "La povniveloj de ĉambranoj estas:"; From 424ccf15df37b062bdc8cb60d427410b217f118a Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 4 Aug 2023 18:35:22 +0000 Subject: [PATCH 18/82] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2411 of 2411 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ --- Riot/Assets/uk.lproj/Vector.strings | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index bac3f4c30..22f63ba25 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -2962,3 +2962,8 @@ "room_command_change_display_name_description" = "Змінює ваш нік"; "notice_display_name_changed_to" = "%@ змінили своє показуване ім'я на %@"; "poll_timeline_loading" = "Завантаження..."; +"room_creation_user_not_found_prompt_invite_action" = "Усе одно розпочати приватну бесіду"; +"room_participants_invite_anyway" = "Усе одно запросити"; +"room_creation_user_not_found_prompt_title" = "Підтвердження"; +"room_creation_user_not_found_prompt_message" = "Не вдалося знайти профілі для цього Matrix ID. Усе одно хочете розпочати приватну бесіду?"; +"room_participants_invite_unknown_participant_prompt_to_msg" = "Не вдалося знайти профілі для цього Matrix ID. Ви впевнені, що хочете запросити %@ до %@?"; From c3c761c933998e8dab69c0d1b0c780ffab5a68f6 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Sat, 5 Aug 2023 22:46:12 +0000 Subject: [PATCH 19/82] Translated using Weblate (Slovak) Currently translated at 100.0% (2411 of 2411 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ --- Riot/Assets/sk.lproj/Vector.strings | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Riot/Assets/sk.lproj/Vector.strings b/Riot/Assets/sk.lproj/Vector.strings index 91e52cee0..061991f16 100644 --- a/Riot/Assets/sk.lproj/Vector.strings +++ b/Riot/Assets/sk.lproj/Vector.strings @@ -2960,3 +2960,8 @@ "room_command_change_display_name_description" = "Mení vaše zobrazované meno / prezývku"; "notice_display_name_changed_to" = "%@ zmenil/a svoje zobrazované meno na %@"; "poll_timeline_loading" = "Načítavanie…"; +"room_creation_user_not_found_prompt_title" = "Potvrdenie"; +"room_creation_user_not_found_prompt_invite_action" = "Spustiť konverzáciu aj tak"; +"room_participants_invite_anyway" = "Napriek tomu pozvať"; +"room_creation_user_not_found_prompt_message" = "Nie je možné nájsť používateľské profily pre toto Matrix ID. Chcete aj tak poslať priamu správu?"; +"room_participants_invite_unknown_participant_prompt_to_msg" = "Nie je možné nájsť profily pre toto Matrix ID. Ste si istí, že chcete pozvať %@ do %@?"; From cb775dcb4e7b8cb97f083934cd6dda02ca30e7f1 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 8 Aug 2023 21:12:48 +0000 Subject: [PATCH 20/82] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2411 of 2411 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ --- Riot/Assets/uk.lproj/Vector.strings | 50 ++++++++++++++--------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index 22f63ba25..d5113e3c7 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -184,7 +184,7 @@ "settings_remove_prompt_title" = "Підтвердження"; "settings_surname" = "Прізвище"; "settings_first_name" = "Ім’я"; -"settings_display_name" = "Показуване ім’я"; +"settings_display_name" = "Псевдонім"; "settings_profile_picture" = "Зображення профілю"; "settings_sign_out_e2e_warn" = "Ви втратите всі ваші ключі наскрізного шифрування. Це означає що ви більше не будете мати змогу читати старі повідомлення у зашифрованих кімнатах на цьому пристрої."; "settings_sign_out_confirmation" = "Ви впевнені?"; @@ -433,7 +433,7 @@ "room_widget_permission_theme_permission" = "Ваша тема"; "room_widget_permission_user_id_permission" = "Ваш ID користувача"; "room_widget_permission_avatar_url_permission" = "URL-адреса вашого аватара"; -"room_widget_permission_display_name_permission" = "Ваше показуване імʼя"; +"room_widget_permission_display_name_permission" = "Ваш псевдонім"; "room_widget_permission_creator_info_title" = "Цей віджет додано:"; // Room widget permissions @@ -1129,7 +1129,7 @@ "settings_show_url_previews" = "Попередній перегляд вебсайтів"; "settings_ui_theme_picker_message_match_system_theme" = "«Авто» застосовує тему вашого пристрою"; "settings_ui_theme_picker_message_invert_colours" = "«Авто» застосовує налаштування вашого пристрою «Інвертувати кольори»"; -"settings_messages_containing_display_name" = "Моє показуване імʼя"; +"settings_messages_containing_display_name" = "Мій псевдонім"; "settings_discovery_three_pid_details_title_phone_number" = "Керувати номером телефону"; "settings_discovery_three_pid_details_title_email" = "Керувати е-поштою"; "settings_discovery_error_message" = "Сталася помилка. Повторіть спробу."; @@ -1866,7 +1866,7 @@ "login_prompt_email_token" = "Введіть ключ підтвердження електронної пошти:"; "login_email_placeholder" = "Адреса е-пошти"; "login_email_info" = "Вкажіть адресу електронної пошти, щоб інші користувачі могли легше знаходити вас на Matrix і надання вам можливості відновити пароль у майбутньому."; -"login_display_name_placeholder" = "Видиме ім'я (наприклад Bob Obson)"; +"login_display_name_placeholder" = "Псевдонім (наприклад Bob Obson)"; "login_optional_field" = "необов'язково"; "login_password_placeholder" = "Пароль"; "login_user_id_placeholder" = "Ідентифікатор Matrix (наприклад @bob:matrix.org або bob)"; @@ -2113,10 +2113,10 @@ "notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ робить майбутню історію повідомлень видимою всім від часу їхнього приєднання."; "notice_room_history_visible_to_members_from_joined_point" = "%@ робить майбутню історію кімнати видимою всім учасникам кімнати від часу їхнього приєднання."; "notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ робить майбутню історію повідомлень видимою всім від часу їхнього запрошення."; -"notice_room_history_visible_to_members_from_invited_point" = "%@ робить майбутню історію кімнати видимою усім учасникам кімнати від часу їхнього запрошення."; -"notice_room_history_visible_to_members_for_dm" = "%@ робить майбутню історію повідомлень видимою усім учасникам кімнати."; -"notice_room_history_visible_to_members" = "%@ робить майбутню історію кімнати видимою усім учасникам кімнати."; -"notice_room_history_visible_to_anyone" = "%@ робить майбутню історію кімнати видимою усім."; +"notice_room_history_visible_to_members_from_invited_point" = "%@ робить майбутню історію кімнати видимою всім учасникам кімнати від часу їхнього запрошення."; +"notice_room_history_visible_to_members_for_dm" = "%@ робить майбутню історію повідомлень видимою всім учасникам кімнати."; +"notice_room_history_visible_to_members" = "%@ робить майбутню історію кімнати видимою всім учасникам кімнати."; +"notice_room_history_visible_to_anyone" = "%@ робить майбутню історію кімнати видимою всім."; "notice_redaction" = "%@ редагує подію (id: %@)"; "notice_feedback" = "Подія відгуку (id: %@): %@"; "notice_room_related_groups" = "Групи пов'язані з цією кімнатою: %@"; @@ -2136,9 +2136,9 @@ "notice_room_name_changed_for_dm" = "%@ змінює назву на %@."; "notice_room_name_changed" = "%@ змінює назву кімнати на %@."; "notice_topic_changed" = "%@ змінює тему на «%@»."; -"notice_display_name_removed" = "%@ вилучає своє показуване ім'я"; -"notice_display_name_changed_from" = "%@ змінює своє показуване ім'я з %@ на %@"; -"notice_display_name_set" = "%@ встановлює своїм показуваним іменем %@"; +"notice_display_name_removed" = "%@ вилучає свій псевдонім"; +"notice_display_name_changed_from" = "%@ змінює свій псевдонім з %@ на %@"; +"notice_display_name_set" = "%@ встановлює своїм псевдонімом %@"; "notice_room_withdraw" = "%@ анульовує запрошення для %@"; "notice_room_kick" = "%@ вилучає %@"; "notice_room_reject" = "%@ відхиляє запрошення"; @@ -2194,7 +2194,7 @@ "notification_settings_per_room_notifications" = "Сповіщення від кожної кімнати"; "notification_settings_per_sender_notifications" = "Сповіщення про кожного відправника"; "notification_settings_contain_my_user_name" = "Сповіщати звуком про повідомлення, що містять моє ім'я користиувача"; -"notification_settings_contain_my_display_name" = "Сповіщати звуком про повідомлення, що містять моє показуване ім'я"; +"notification_settings_contain_my_display_name" = "Сповіщати звуком про повідомлення з моїм псевдонімом"; "notification_settings_just_sent_to_me" = "Сповіщати звуком про надіслані лише мені повідомлення"; "notification_settings_invite_to_a_new_room" = "Сповіщати про запрошення до нових кімнат"; "notification_settings_people_join_leave_rooms" = "Сповіщати, коли люди приєднуються чи виходять з кімнат"; @@ -2209,7 +2209,7 @@ "account_error_email_wrong_title" = "Неправильна адреса е-пошти"; "account_error_matrix_session_is_not_opened" = "Сеанс Matrix не відкрито"; "account_error_picture_change_failed" = "Не вдалося змінити зображення"; -"account_error_display_name_change_failed" = "Не вдалося змінити показуване ім'я"; +"account_error_display_name_change_failed" = "Не вдалося змінити псевдонім"; "account_msisdn_validation_error" = "Не вдалося перевірити номер телефону."; "account_email_validation_title" = "Очікування перевірки"; "account_msisdn_validation_title" = "Очікування перевірки"; @@ -2241,12 +2241,12 @@ "e2e_passphrase_enter" = "Введіть парольну фразу"; "e2e_passphrase_empty" = "Парольна фраза не повинна бути порожньою"; "e2e_passphrase_confirm" = "Підтвердити парольну фразу"; -"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Ви зробили майбутні повідомлення кімнати видимими будь-кому від часу їхнього приєднання."; -"notice_room_history_visible_to_members_from_joined_point_by_you" = "Ви зробили майбутню історію кімнати видимою усім учасникам кімнати від часу їхнього приєднання."; +"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Ви зробили майбутні повідомлення кімнати видимими всім від часу їхнього приєднання."; +"notice_room_history_visible_to_members_from_joined_point_by_you" = "Ви зробили майбутню історію кімнати видимою всім учасникам кімнати від часу їхнього приєднання."; "notice_room_history_visible_to_members_from_invited_point_by_you_for_dm" = "Ви зробили майбутні повідомлення кімнати видимими будь-кому від часу запрошення їх."; -"notice_room_history_visible_to_members_from_invited_point_by_you" = "Ви зробили майбутню історію кімнати видимою усім учасникам кімнати від часу запрошення їх."; -"notice_room_history_visible_to_members_by_you_for_dm" = "Ви зробили майбутні повідомлення кімнати видимими усім учасникам кімнати."; -"notice_room_history_visible_to_members_by_you" = "Ви зробили майбутню історію кімнати видимою усім учасникам кімнати."; +"notice_room_history_visible_to_members_from_invited_point_by_you" = "Ви зробили майбутню історію кімнати видимою всім учасникам кімнати від часу запрошення їх."; +"notice_room_history_visible_to_members_by_you_for_dm" = "Ви зробили майбутні повідомлення кімнати видимими всім учасникам кімнати."; +"notice_room_history_visible_to_members_by_you" = "Ви зробили майбутню історію кімнати видимою всім учасникам кімнати."; "notice_room_history_visible_to_anyone_by_you" = "Ви зробили майбутню історію кімнати видимою будь-кому."; "notice_room_created_by_you" = "Ви створили й сконфігурували кімнату."; "notice_profile_change_redacted_by_you" = "Ви оновили свій профіль %@"; @@ -2261,9 +2261,9 @@ "notice_room_name_changed_by_you_for_dm" = "Ви змінили назву на %@."; "notice_room_name_changed_by_you" = "Ви змінили назву кімнати на %@."; "notice_topic_changed_by_you" = "Ви змінили тему на «%@»."; -"notice_display_name_removed_by_you" = "Ви вилучили показуване ім'я"; -"notice_display_name_changed_from_by_you" = "Ви змінили показуване ім'я з %@ на %@"; -"notice_display_name_set_by_you" = "Ви вказали показуваним іменем %@"; +"notice_display_name_removed_by_you" = "Ви вилучили псевдонім"; +"notice_display_name_changed_from_by_you" = "Ви змінили псевдонім з %@ на %@"; +"notice_display_name_set_by_you" = "Ви налаштували псевдонімом %@"; "notice_conference_call_finished" = "Голосовий груповий виклик завершено"; "notice_conference_call_started" = "Груповий голосовий виклик розпочато"; "notice_conference_call_request" = "%@ запрошує до групового голосового виклику"; @@ -2467,11 +2467,11 @@ "onboarding_avatar_accessibility_label" = "Зображення профілю"; "onboarding_avatar_message" = "Час додати обличчя до імені"; "onboarding_avatar_title" = "Додати зображення профілю"; -"onboarding_display_name_max_length" = "Ваше показуване ім'я повинно складатися з менш ніж 256 символів"; +"onboarding_display_name_max_length" = "Ваш псевдонім повинен складатися з менш ніж 256 символів"; "onboarding_display_name_hint" = "Ви можете змінити його пізніше"; -"onboarding_display_name_placeholder" = "Показуване ім'я"; +"onboarding_display_name_placeholder" = "Псевдонім"; "onboarding_display_name_message" = "Його буде показано у надісланих повідомленнях."; -"onboarding_display_name_title" = "Виберіть показуване ім'я"; +"onboarding_display_name_title" = "Оберіть псевдонім"; "onboarding_personalization_skip" = "Пропустити цей крок"; "onboarding_personalization_save" = "Зберегти й продовжити"; "onboarding_congratulations_home_button" = "На головну"; @@ -2960,7 +2960,7 @@ // Room commands descriptions "room_command_change_display_name_description" = "Змінює ваш нік"; -"notice_display_name_changed_to" = "%@ змінили своє показуване ім'я на %@"; +"notice_display_name_changed_to" = "%@ змінили свій псевдонім на %@"; "poll_timeline_loading" = "Завантаження..."; "room_creation_user_not_found_prompt_invite_action" = "Усе одно розпочати приватну бесіду"; "room_participants_invite_anyway" = "Усе одно запросити"; From 055856e2572af023b65efe6c641016c2a4ed5399 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 15 Aug 2023 10:12:16 +0100 Subject: [PATCH 21/82] changelog.d: Upgrade MatrixSDK version ([v0.27.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.0)). --- Config/AppVersion.xcconfig | 4 ++-- Podfile | 2 +- changelog.d/x-nolink-0.change | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changelog.d/x-nolink-0.change diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 46c0099fd..ec41e80dc 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.10.15 -CURRENT_PROJECT_VERSION = 1.10.15 +MARKETING_VERSION = 1.11.0 +CURRENT_PROJECT_VERSION = 1.11.0 diff --git a/Podfile b/Podfile index fda86b718..581fc76b0 100644 --- a/Podfile +++ b/Podfile @@ -16,7 +16,7 @@ use_frameworks! # - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixSDKVersion = '= 0.26.12' +$matrixSDKVersion = '= 0.27.0' # $matrixSDKVersion = :local # $matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change new file mode 100644 index 000000000..f3e3d6e74 --- /dev/null +++ b/changelog.d/x-nolink-0.change @@ -0,0 +1 @@ +Upgrade MatrixSDK version ([v0.27.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.0)). \ No newline at end of file From 074f9853c8f56e52fa1658338243666649d09b90 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 15 Aug 2023 10:12:16 +0100 Subject: [PATCH 22/82] version++ --- CHANGES.md | 13 +++++++++++++ changelog.d/7618.change | 1 - changelog.d/7633.bugfix | 1 - changelog.d/pr-7639.bugfix | 1 - changelog.d/x-nolink-0.change | 1 - 5 files changed, 13 insertions(+), 4 deletions(-) delete mode 100644 changelog.d/7618.change delete mode 100644 changelog.d/7633.bugfix delete mode 100644 changelog.d/pr-7639.bugfix delete mode 100644 changelog.d/x-nolink-0.change diff --git a/CHANGES.md b/CHANGES.md index d2e289625..851c844d4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,16 @@ +## Changes in 1.11.0 (2023-08-15) + +🙌 Improvements + +- Upgrade MatrixSDK version ([v0.27.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.0)). +- Disable Siri, Share and Reply (from NSE) Extensions. ([#7618](https://github.com/vector-im/element-ios/issues/7618)) + +🐛 Bugfixes + +- Fix bug in SSO URL generation that was non-compliant with the spec. ([#7639](https://github.com/vector-im/element-ios/pull/7639)) +- Fix a crash when opening v11 rooms. ([#7633](https://github.com/vector-im/element-ios/issues/7633)) + + ## Changes in 1.10.14 (2023-06-21) 🙌 Improvements diff --git a/changelog.d/7618.change b/changelog.d/7618.change deleted file mode 100644 index 0cefa0983..000000000 --- a/changelog.d/7618.change +++ /dev/null @@ -1 +0,0 @@ -Disable Siri, Share and Reply (from NSE) Extensions. \ No newline at end of file diff --git a/changelog.d/7633.bugfix b/changelog.d/7633.bugfix deleted file mode 100644 index b7345971e..000000000 --- a/changelog.d/7633.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix a crash when opening v11 rooms. \ No newline at end of file diff --git a/changelog.d/pr-7639.bugfix b/changelog.d/pr-7639.bugfix deleted file mode 100644 index 9bbf73b23..000000000 --- a/changelog.d/pr-7639.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix bug in SSO URL generation that was non-compliant with the spec. \ No newline at end of file diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change deleted file mode 100644 index f3e3d6e74..000000000 --- a/changelog.d/x-nolink-0.change +++ /dev/null @@ -1 +0,0 @@ -Upgrade MatrixSDK version ([v0.27.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.0)). \ No newline at end of file From 68ca2f99721e7ee14a20ca717c0ec54f7dba766b Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 15 Aug 2023 10:18:04 +0100 Subject: [PATCH 23/82] Add missing changelog entry. --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 851c844d4..ee4133fae 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ ## Changes in 1.11.0 (2023-08-15) +✨ Features + +- Integrate Device Dehydration v2 through the Crypto SDK ([#7630](https://github.com/vector-im/element-ios/pull/7630)) + 🙌 Improvements - Upgrade MatrixSDK version ([v0.27.0](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.0)). From 0ba26db75531d505022dc1ffd02d26f0cf192550 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 15 Aug 2023 11:09:55 +0100 Subject: [PATCH 24/82] finish version++ --- Podfile.lock | 22 +++++++++---------- .../xcshareddata/xcschemes/Riot.xcscheme | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index fb9bd48ae..988424488 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -39,20 +39,20 @@ PODS: - LoggerAPI (1.9.200): - Logging (~> 1.1) - Logging (1.4.0) - - MatrixSDK (0.26.12): - - MatrixSDK/Core (= 0.26.12) - - MatrixSDK/Core (0.26.12): + - MatrixSDK (0.27.0): + - MatrixSDK/Core (= 0.27.0) + - MatrixSDK/Core (0.27.0): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) - - MatrixSDKCrypto (= 0.3.4) + - MatrixSDKCrypto (= 0.3.12) - OLMKit (~> 3.2.5) - Realm (= 10.27.0) - SwiftyBeaver (= 1.9.5) - - MatrixSDK/JingleCallStack (0.26.12): + - MatrixSDK/JingleCallStack (0.27.0): - JitsiMeetSDKLite (= 8.1.2-lite) - MatrixSDK/Core - - MatrixSDKCrypto (0.3.4) + - MatrixSDKCrypto (0.3.12) - OLMKit (3.2.12): - OLMKit/olmc (= 3.2.12) - OLMKit/olmcpp (= 3.2.12) @@ -102,8 +102,8 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.26.12) - - MatrixSDK/JingleCallStack (= 0.26.12) + - MatrixSDK (= 0.27.0) + - MatrixSDK/JingleCallStack (= 0.27.0) - OLMKit - PostHog (~> 2.0.0) - ReadMoreTextView (~> 3.0.1) @@ -187,8 +187,8 @@ SPEC CHECKSUMS: libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b - MatrixSDK: 0af737bc461b82d0ec9edd6fdf8f70b02771ebd3 - MatrixSDKCrypto: ac805c22c24f79f349cdbfa065855c73a4c81b51 + MatrixSDK: 981c0e6abd40f0882e1316923edb2b3966d29513 + MatrixSDKCrypto: 25929a40733b4ab54f659aaf6a730552a0a06504 OLMKit: da115f16582e47626616874e20f7bb92222c7a51 PostHog: 660ec6c9d80cec17b685e148f17f6785a88b597d ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d @@ -208,6 +208,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: f8d711a31048bfd5981d17b00487a85996b62983 +PODFILE CHECKSUM: f8f66ce4fa24937192a493d3a0a5c2c2e429c8fc COCOAPODS: 1.11.3 diff --git a/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme b/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme index 012a5a109..52ec9a4e1 100644 --- a/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme +++ b/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme @@ -1,6 +1,6 @@ Date: Tue, 15 Aug 2023 11:10:01 +0100 Subject: [PATCH 25/82] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index ec41e80dc..37b603c0b 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.11.0 -CURRENT_PROJECT_VERSION = 1.11.0 +MARKETING_VERSION = 1.11.1 +CURRENT_PROJECT_VERSION = 1.11.1 From 102e7e9c764d0a987e381a51081d87773475af18 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 21 Aug 2023 09:58:50 +0300 Subject: [PATCH 26/82] Update introspect to the latest version, remove now duplicate `introspectCollectionView` --- Podfile.lock | 6 +++--- RiotSwiftUI/Modules/Common/Util/ListBackground.swift | 6 ------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 988424488..561af4bce 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -26,7 +26,7 @@ PODS: - GBDeviceInfo/Core (= 7.1.0) - GBDeviceInfo/Core (7.1.0) - GZIP (1.3.0) - - Introspect (0.1.4) + - Introspect (0.11.0) - JitsiMeetSDKLite (8.1.2-lite): - JitsiWebRTC (~> 111.0) - JitsiWebRTC (111.0.2) @@ -177,7 +177,7 @@ SPEC CHECKSUMS: FlowCommoniOS: ca92071ab526dc89905495a37844fd7e78d1a7f2 GBDeviceInfo: 5d62fa85bdcce3ed288d83c28789adf1173e4376 GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3 - Introspect: b62c4dd2063072327c21d618ef2bedc3c87bc366 + Introspect: 4cc1e4c34dd016540c8d86a591c231c09dafbee3 JitsiMeetSDKLite: 895213158cf62342069a10634a41d2f1c00057f7 JitsiWebRTC: 80f62908fcf2a1160e0d14b584323fb6e6be630b KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51 @@ -210,4 +210,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: f8f66ce4fa24937192a493d3a0a5c2c2e429c8fc -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/RiotSwiftUI/Modules/Common/Util/ListBackground.swift b/RiotSwiftUI/Modules/Common/Util/ListBackground.swift index d4e087da8..ddfc362c2 100644 --- a/RiotSwiftUI/Modules/Common/Util/ListBackground.swift +++ b/RiotSwiftUI/Modules/Common/Util/ListBackground.swift @@ -49,10 +49,4 @@ extension View { func listBackgroundColor(_ color: Color) -> some View { modifier(ListBackgroundModifier(color: color)) } - - /// Finds a `UICollectionView` from a `SwiftUI.List`, or `SwiftUI.List` child. - /// Stop gap until https://github.com/siteline/SwiftUI-Introspect/pull/169 - func introspectCollectionView(customize: @escaping (UICollectionView) -> Void) -> some View { - introspect(selector: TargetViewSelector.ancestorOrSiblingContaining, customize: customize) - } } From e2676c46376c7887949bcf5474625f27698177a3 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 21 Aug 2023 10:03:23 +0300 Subject: [PATCH 27/82] Prevent pill crashes when room members are missing display names (objc interop) --- Riot/Modules/Pills/PillsFormatter.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Riot/Modules/Pills/PillsFormatter.swift b/Riot/Modules/Pills/PillsFormatter.swift index 1b6256835..ecdfac5fe 100644 --- a/Riot/Modules/Pills/PillsFormatter.swift +++ b/Riot/Modules/Pills/PillsFormatter.swift @@ -197,12 +197,14 @@ class PillsFormatter: NSObject { guard let roomMember = roomState.members.member(withUserId: userId) else { return } + + let displayName = roomMember.displayname ?? userId pill.data?.items = [ .avatar(url: roomMember.avatarUrl, - string: roomMember.displayname, - matrixId: roomMember.userId), - .text(roomMember.displayname) + string: displayName, + matrixId: userId), + .text(displayName) ] default: break From 382da2147b4dcabf61c500c07c77902514fe504b Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 21 Aug 2023 14:46:09 +0300 Subject: [PATCH 28/82] Prevent mention crashes when room members are missing display names (objc interop) --- .../WYSIWYGInputToolbar/WysiwygInputToolbarView.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift index edd951fd6..60b6edf94 100644 --- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift +++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift @@ -207,8 +207,14 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp } func mention(_ member: MXRoomMember) { - self.wysiwygViewModel.setMention(url: MXTools.permalinkToUser(withUserId: member.userId), - name: member.displayname, + guard let userId = member.userId else { + return + } + + let displayName = member.displayname ?? userId + + self.wysiwygViewModel.setMention(url: MXTools.permalinkToUser(withUserId: userId), + name: displayName, mentionType: .user) } From 332b152cdfabff9c294c185a7056571ccae8e7e7 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 23 Aug 2023 16:02:04 +0300 Subject: [PATCH 29/82] Add changelogs --- changelog.d/7651.bugfix | 1 + changelog.d/pr-7649.bugfix | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog.d/7651.bugfix create mode 100644 changelog.d/pr-7649.bugfix diff --git a/changelog.d/7651.bugfix b/changelog.d/7651.bugfix new file mode 100644 index 000000000..4f97c24a7 --- /dev/null +++ b/changelog.d/7651.bugfix @@ -0,0 +1 @@ +Prevent pill crashes when room members are missing display names (objc interop) \ No newline at end of file diff --git a/changelog.d/pr-7649.bugfix b/changelog.d/pr-7649.bugfix new file mode 100644 index 000000000..08e9bebec --- /dev/null +++ b/changelog.d/pr-7649.bugfix @@ -0,0 +1 @@ +Prevent mention crashes when room members are missing display names (objc interop) \ No newline at end of file From 4ddafd63eebc77d3da31a791716f704fa76c9d52 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Thu, 24 Aug 2023 17:18:13 +0200 Subject: [PATCH 30/82] Hide deactivate account if the auth property is present on the WK. --- Riot/Modules/Settings/SettingsViewController.m | 2 +- changelog.d/7648.bugfix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/7648.bugfix diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index be87bea3b..c50bc3c35 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -608,7 +608,7 @@ ChangePasswordCoordinatorBridgePresenterDelegate> } } - if (BuildSettings.settingsScreenAllowDeactivatingAccount) + if (BuildSettings.settingsScreenAllowDeactivatingAccount && !self.mainSession.homeserverWellknown.authentication) { Section *sectionDeactivate = [Section sectionWithTag:SECTION_TAG_DEACTIVATE_ACCOUNT]; [sectionDeactivate addRowWithTag:0]; diff --git a/changelog.d/7648.bugfix b/changelog.d/7648.bugfix new file mode 100644 index 000000000..c8f48546c --- /dev/null +++ b/changelog.d/7648.bugfix @@ -0,0 +1 @@ +Deactivate account is hidden for servers with OIDC auth. \ No newline at end of file From e718c0400e53b100934029596bfa333cfd51b048 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Thu, 24 Aug 2023 17:53:48 +0200 Subject: [PATCH 31/82] completed --- Riot/Modules/Settings/SettingsViewController.m | 5 ++++- changelog.d/7645.bugfix | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelog.d/7645.bugfix diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index be87bea3b..9f818a988 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -365,7 +365,10 @@ ChangePasswordCoordinatorBridgePresenterDelegate> { [sectionUserSettings addRowWithTag: USER_SETTINGS_PHONENUMBERS_OFFSET + index]; } - if (BuildSettings.settingsScreenAllowAddingEmailThreepids) + if (BuildSettings.settingsScreenAllowAddingEmailThreepids && + // If the threePidChanges is nil we assume the capability to be true + (!self.mainSession.homeserverCapabilities.threePidChanges || + self.mainSession.homeserverCapabilities.threePidChanges.enabled)) { [sectionUserSettings addRowWithTag:USER_SETTINGS_ADD_EMAIL_INDEX]; } diff --git a/changelog.d/7645.bugfix b/changelog.d/7645.bugfix new file mode 100644 index 000000000..b546db4f9 --- /dev/null +++ b/changelog.d/7645.bugfix @@ -0,0 +1 @@ +Add email UI is hidden if the 3 pid changes capability is disabled. \ No newline at end of file From 4c51c952c41123eccd8e96f4ece2c4169edb5762 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Thu, 24 Aug 2023 19:15:12 +0200 Subject: [PATCH 32/82] added the cell, now I just need to implement the navigation to the web view --- Riot/Assets/en.lproj/Vector.strings | 3 ++ Riot/Generated/Strings.swift | 12 ++++++ .../Modules/Settings/SettingsViewController.m | 41 +++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 132e5dd25..1856fdb9f 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -748,6 +748,9 @@ Tap the + to start adding people."; "settings_three_pids_management_information_part1" = "Manage which email addresses or phone numbers you can use to log in or recover your account here. Control who can find you in "; "settings_three_pids_management_information_part2" = "Discovery"; "settings_three_pids_management_information_part3" = "."; +"settings_manage_account_title" = "Account"; +"settings_manage_account_action" = "Manage"; +"settings_manage_account_description" = "Manage your account at %@"; "settings_confirm_media_size" = "Confirm size when sending"; "settings_confirm_media_size_description" = "When this is on, you’ll be asked to confirm what size images and videos will be sent as."; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 1c2516fe9..cc34c01bb 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -7791,6 +7791,18 @@ public class VectorL10n: NSObject { public static var settingsLinks: String { return VectorL10n.tr("Vector", "settings_links") } + /// Manage + public static var settingsManageAccountAction: String { + return VectorL10n.tr("Vector", "settings_manage_account_action") + } + /// Manage your account at %@ + public static func settingsManageAccountDescription(_ p1: String) -> String { + return VectorL10n.tr("Vector", "settings_manage_account_description", p1) + } + /// Account + public static var settingsManageAccountTitle: String { + return VectorL10n.tr("Vector", "settings_manage_account_title") + } /// Mark all messages as read public static var settingsMarkAllAsRead: String { return VectorL10n.tr("Vector", "settings_mark_all_as_read") diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index be87bea3b..86aec19e8 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -50,6 +50,7 @@ typedef NS_ENUM(NSUInteger, SECTION_TAG) { SECTION_TAG_SIGN_OUT = 0, SECTION_TAG_USER_SETTINGS, + SECTION_TAG_ACCOUNT, SECTION_TAG_SENDING_MEDIA, SECTION_TAG_LINKS, SECTION_TAG_SECURITY, @@ -185,6 +186,11 @@ typedef NS_ENUM(NSUInteger, SECURITY) DEVICE_MANAGER_INDEX }; +typedef NS_ENUM(NSUInteger, ACCOUNT) +{ + ACCOUNT_MANAGE_INDEX = 0, +}; + typedef void (^blockSettingsViewController_onReadyToDestroy)(void); #pragma mark - SettingsViewController @@ -383,6 +389,16 @@ ChangePasswordCoordinatorBridgePresenterDelegate> sectionUserSettings.headerTitle = [VectorL10n settingsUserSettings]; [tmpSections addObject:sectionUserSettings]; + + NSString *manageAccountURL = self.mainSession.homeserverWellknown.authentication.account; + if (manageAccountURL) + { + Section *account = [Section sectionWithTag: SECTION_TAG_ACCOUNT]; + [account addRowWithTag:ACCOUNT_MANAGE_INDEX]; + account.headerTitle = [VectorL10n settingsManageAccountTitle]; + account.footerTitle = [VectorL10n settingsManageAccountDescription:manageAccountURL]; + [tmpSections addObject:account]; + } if (BuildSettings.settingsScreenShowConfirmMediaSize) { @@ -2626,6 +2642,17 @@ ChangePasswordCoordinatorBridgePresenterDelegate> cell = deactivateAccountBtnCell; } + else if (section == SECTION_TAG_ACCOUNT) + { + switch (row) + { + case ACCOUNT_MANAGE_INDEX: + cell = [self getDefaultTableViewCell:tableView]; + cell.textLabel.text = [VectorL10n settingsManageAccountAction]; + [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; + break; + } + } return cell; } @@ -2975,6 +3002,14 @@ ChangePasswordCoordinatorBridgePresenterDelegate> break; } } + else if (section == SECTION_TAG_ACCOUNT) + { + switch(row) { + case ACCOUNT_MANAGE_INDEX: + [self onManageAccountTap]; + break; + } + } [tableView deselectRowAtIndexPath:indexPath animated:YES]; } @@ -3883,6 +3918,12 @@ ChangePasswordCoordinatorBridgePresenterDelegate> } } +- (void)onManageAccountTap +{ + // TODO + // Open a web view with the account url +} + - (void)showThemePicker { __weak typeof(self) weakSelf = self; From f42e1a38faa143e8bb1b9e45aecb6179fb70a730 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Fri, 25 Aug 2023 12:21:38 +0200 Subject: [PATCH 33/82] web view opened on tap + changelog --- Riot/Assets/en.lproj/Vector.strings | 2 +- Riot/Modules/Settings/SettingsViewController.m | 7 +++++-- changelog.d/7653.feature | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelog.d/7653.feature diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 1856fdb9f..89d86d336 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -749,7 +749,7 @@ Tap the + to start adding people."; "settings_three_pids_management_information_part2" = "Discovery"; "settings_three_pids_management_information_part3" = "."; "settings_manage_account_title" = "Account"; -"settings_manage_account_action" = "Manage"; +"settings_manage_account_action" = "Manage account"; "settings_manage_account_description" = "Manage your account at %@"; "settings_confirm_media_size" = "Confirm size when sending"; diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 86aec19e8..6a29ddf6b 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -3920,8 +3920,11 @@ ChangePasswordCoordinatorBridgePresenterDelegate> - (void)onManageAccountTap { - // TODO - // Open a web view with the account url + NSURL *url = [NSURL URLWithString: self.mainSession.homeserverWellknown.authentication.account]; + if (url) { + SFSafariViewController *accountVC = [[SFSafariViewController alloc] initWithURL: url]; + [self presentViewController:accountVC animated:YES completion:nil]; + } } - (void)showThemePicker diff --git a/changelog.d/7653.feature b/changelog.d/7653.feature new file mode 100644 index 000000000..cabf7402b --- /dev/null +++ b/changelog.d/7653.feature @@ -0,0 +1 @@ +New settings cell to manage your account through MAS if the home server allows it. \ No newline at end of file From cb34eaf6c6772211fc2404abbcd35a20165de3f9 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Fri, 25 Aug 2023 13:02:06 +0200 Subject: [PATCH 34/82] opening the safari web view externally so that it will be able to share the cookies --- Riot/Generated/Strings.swift | 2 +- Riot/Modules/Settings/SettingsViewController.m | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index cc34c01bb..373f65fbd 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -7791,7 +7791,7 @@ public class VectorL10n: NSObject { public static var settingsLinks: String { return VectorL10n.tr("Vector", "settings_links") } - /// Manage + /// Manage account public static var settingsManageAccountAction: String { return VectorL10n.tr("Vector", "settings_manage_account_action") } diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 6a29ddf6b..bbacd4980 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -3922,8 +3922,7 @@ ChangePasswordCoordinatorBridgePresenterDelegate> { NSURL *url = [NSURL URLWithString: self.mainSession.homeserverWellknown.authentication.account]; if (url) { - SFSafariViewController *accountVC = [[SFSafariViewController alloc] initWithURL: url]; - [self presentViewController:accountVC animated:YES completion:nil]; + [UIApplication.sharedApplication openURL:url options:@{} completionHandler:nil]; } } From 1126891feacca49156358d4b06688a43964a8e4d Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Fri, 25 Aug 2023 17:22:11 +0200 Subject: [PATCH 35/82] fix 7646 --- .../ManageSession/ManageSessionViewController.m | 14 ++++++++++++++ .../Coordinator/UserSessionsFlowCoordinator.swift | 11 ++++++++++- changelog.d/7646.bugfix | 1 + 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 changelog.d/7646.bugfix diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index 806e10cff..fa44c7050 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -655,6 +655,20 @@ enum { } - (void)removeDevice +{ + NSURL *logoutURL = [self.mainSession.homeserverWellknown.authentication getMasLogoutDeviceURLFromDeviceID:device.deviceId]; + if (logoutURL) + { + [UIApplication.sharedApplication openURL:logoutURL options:@{} completionHandler:nil]; + [self withdrawViewControllerAnimated:YES completion:nil]; + } + else + { + [self removeDeviceThroughAPI]; + } +} + +-(void) removeDeviceThroughAPI { [self startActivityIndicator]; self.view.userInteractionEnabled = NO; diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index 79fd9e573..a3fc8e212 100644 --- a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift @@ -123,7 +123,11 @@ final class UserSessionsFlowCoordinator: NSObject, Coordinator, Presentable { if sessionInfo.isCurrent { self.showLogoutConfirmationForCurrentSession() } else { - self.showLogoutConfirmation(for: [sessionInfo]) + if let logoutURL = self.parameters.session.homeserverWellknown.authentication?.getMasLogoutDeviceURL(fromDeviceID: sessionInfo.id) { + self.openMasLogoutURL(logoutURL) + } else { + self.showLogoutConfirmation(for: [sessionInfo]) + } } case let .showSessionStateInfo(sessionInfo): self.showInfoSheet(parameters: .init(userSessionInfo: sessionInfo, parentSize: self.toPresentable().view.bounds.size)) @@ -182,6 +186,11 @@ final class UserSessionsFlowCoordinator: NSObject, Coordinator, Presentable { return UserOtherSessionsCoordinator(parameters: parameters) } + private func openMasLogoutURL(_ url: URL) { + UIApplication.shared.open(url) + popToSessionsOverview() + } + /// Shows a confirmation dialog to the user to sign out of a session. private func showLogoutConfirmation(for sessionInfos: [UserSessionInfo]) { // Use a UIAlertController as we don't have confirmationDialog in SwiftUI on iOS 14. diff --git a/changelog.d/7646.bugfix b/changelog.d/7646.bugfix new file mode 100644 index 000000000..aaae87297 --- /dev/null +++ b/changelog.d/7646.bugfix @@ -0,0 +1 @@ +You can now log out from other sessions using MAS on supported OIDC home servers. \ No newline at end of file From fbd0793b6f11afb0ba6cf807998ccbdc1e86a768 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Fri, 25 Aug 2023 19:39:55 +0200 Subject: [PATCH 36/82] code improvement --- .../Security/ManageSession/ManageSessionViewController.m | 2 +- .../UserSessions/Coordinator/UserSessionsFlowCoordinator.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index fa44c7050..a3c1e431e 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -656,7 +656,7 @@ enum { - (void)removeDevice { - NSURL *logoutURL = [self.mainSession.homeserverWellknown.authentication getMasLogoutDeviceURLFromDeviceID:device.deviceId]; + NSURL *logoutURL = [self.mainSession.homeserverWellknown.authentication getLogoutDeviceURLFromID:device.deviceId]; if (logoutURL) { [UIApplication.sharedApplication openURL:logoutURL options:@{} completionHandler:nil]; diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index a3fc8e212..acf6a3083 100644 --- a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift @@ -123,7 +123,7 @@ final class UserSessionsFlowCoordinator: NSObject, Coordinator, Presentable { if sessionInfo.isCurrent { self.showLogoutConfirmationForCurrentSession() } else { - if let logoutURL = self.parameters.session.homeserverWellknown.authentication?.getMasLogoutDeviceURL(fromDeviceID: sessionInfo.id) { + if let logoutURL = self.parameters.session.homeserverWellknown.authentication?.getLogoutDeviceURL(fromID: sessionInfo.id) { self.openMasLogoutURL(logoutURL) } else { self.showLogoutConfirmation(for: [sessionInfo]) From be289a37903a5386a2a3fec00a4a5007646a27bc Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Mon, 28 Aug 2023 12:20:26 +0200 Subject: [PATCH 37/82] completed --- Riot/Assets/en.lproj/Vector.strings | 2 + Riot/Generated/Strings.swift | 8 ++++ .../ManageSessionViewController.m | 40 ++++++++++++++-- .../UserSessionsFlowCoordinator.swift | 47 ++++++++++++++----- 4 files changed, 81 insertions(+), 16 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 89d86d336..e1244e81e 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -967,6 +967,8 @@ Tap the + to start adding people."; "manage_session_trusted" = "Trusted by you"; "manage_session_not_trusted" = "Not trusted"; "manage_session_sign_out" = "Sign out of this session"; +"manage_session_redirect" = "You will be redirected to your server's authentication provider to complete sign out."; +"manage_session_redirect_error" = "Functionality currently unavailable. Please contact your homeserver admin"; "manage_session_rename" = "Rename session"; "manage_session_sign_out_other_sessions" = "Sign out of all other sessions"; // User sessions management diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 373f65fbd..aa3ca9d72 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -3667,6 +3667,14 @@ public class VectorL10n: NSObject { public static var manageSessionNotTrusted: String { return VectorL10n.tr("Vector", "manage_session_not_trusted") } + /// You will be redirected to your server's authentication provider to complete sign out. + public static var manageSessionRedirect: String { + return VectorL10n.tr("Vector", "manage_session_redirect") + } + /// Functionality currently unavailable. Please contact your homeserver admin + public static var manageSessionRedirectError: String { + return VectorL10n.tr("Vector", "manage_session_redirect_error") + } /// Rename session public static var manageSessionRename: String { return VectorL10n.tr("Vector", "manage_session_rename") diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index a3c1e431e..e1fd5c5e8 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -656,11 +656,18 @@ enum { - (void)removeDevice { - NSURL *logoutURL = [self.mainSession.homeserverWellknown.authentication getLogoutDeviceURLFromID:device.deviceId]; - if (logoutURL) + MXWellKnownAuthentication *authentication = self.mainSession.homeserverWellknown.authentication; + if (authentication) { - [UIApplication.sharedApplication openURL:logoutURL options:@{} completionHandler:nil]; - [self withdrawViewControllerAnimated:YES completion:nil]; + NSURL *logoutURL = [authentication getLogoutDeviceURLFromID:device.deviceId]; + if (logoutURL) + { + [self removeDeviceRedirectWithURL:logoutURL]; + } + else + { + [self showRemoveDeviceRedirectError]; + } } else { @@ -668,6 +675,31 @@ enum { } } +-(void) removeDeviceRedirectWithURL: (NSURL * _Nonnull) url +{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle: [VectorL10n manageSessionRedirect] message: nil preferredStyle:UIAlertControllerStyleAlert]; + + __weak typeof(self) weakSelf = self; + UIAlertAction *action = [UIAlertAction actionWithTitle:[VectorL10n ok] + style:UIAlertActionStyleDefault + handler: ^(UIAlertAction * action) { + [UIApplication.sharedApplication openURL:url options:@{} completionHandler:^(BOOL success) { + if (success && weakSelf) + { + [weakSelf withdrawViewControllerAnimated:YES completion:nil]; + } + }]; + }]; + [alert addAction: action]; + [self presentViewController:alert animated:YES completion:nil]; +} + +-(void) showRemoveDeviceRedirectError +{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle: [VectorL10n manageSessionRedirectError] message: nil preferredStyle:UIAlertControllerStyleAlert]; + [self presentViewController:alert animated:YES completion:nil]; +} + -(void) removeDeviceThroughAPI { [self startActivityIndicator]; diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index acf6a3083..041f5a1ae 100644 --- a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift @@ -120,21 +120,29 @@ final class UserSessionsFlowCoordinator: NSObject, Coordinator, Presentable { case let .renameSession(sessionInfo): self.showRenameSessionScreen(for: sessionInfo) case let .logoutOfSession(sessionInfo): - if sessionInfo.isCurrent { - self.showLogoutConfirmationForCurrentSession() - } else { - if let logoutURL = self.parameters.session.homeserverWellknown.authentication?.getLogoutDeviceURL(fromID: sessionInfo.id) { - self.openMasLogoutURL(logoutURL) - } else { - self.showLogoutConfirmation(for: [sessionInfo]) - } - } + self.handleLogoutOfSession(sessionInfo: sessionInfo) case let .showSessionStateInfo(sessionInfo): self.showInfoSheet(parameters: .init(userSessionInfo: sessionInfo, parentSize: self.toPresentable().view.bounds.size)) } } pushScreen(with: coordinator) } + + private func handleLogoutOfSession(sessionInfo: UserSessionInfo) { + if sessionInfo.isCurrent { + self.showLogoutConfirmationForCurrentSession() + } else { + if let authentication = self.parameters.session.homeserverWellknown.authentication { + if let logoutURL = authentication.getLogoutDeviceURL(fromID: sessionInfo.id) { + self.openDeviceLogoutRedirectURL(logoutURL) + } else { + self.showDeviceLogoutRedirectError() + } + } else { + self.showLogoutConfirmation(for: [sessionInfo]) + } + } + } /// Shows the QR login screen. private func openQRLoginScreen() { @@ -186,9 +194,24 @@ final class UserSessionsFlowCoordinator: NSObject, Coordinator, Presentable { return UserOtherSessionsCoordinator(parameters: parameters) } - private func openMasLogoutURL(_ url: URL) { - UIApplication.shared.open(url) - popToSessionsOverview() + private func openDeviceLogoutRedirectURL(_ url: URL) { + let alert = UIAlertController(title: VectorL10n.manageSessionRedirect, message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: VectorL10n.ok, style: .default) { [weak self] _ in + UIApplication.shared.open(url) { [weak self] success in + guard success else { + return + } + self?.popToSessionsOverview() + } + }) + alert.popoverPresentationController?.sourceView = toPresentable().view + navigationRouter.present(alert, animated: true) + } + + private func showDeviceLogoutRedirectError() { + let alert = UIAlertController(title: VectorL10n.manageSessionRedirectError, message: nil, preferredStyle: .alert) + alert.popoverPresentationController?.sourceView = toPresentable().view + navigationRouter.present(alert, animated: true) } /// Shows a confirmation dialog to the user to sign out of a session. From 4014ef5131752cbb915234d4fd57b88374f89fc9 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 29 Aug 2023 12:20:33 +0300 Subject: [PATCH 38/82] changelog.d: Upgrade MatrixSDK version ([v0.27.1](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.1)). --- Podfile | 2 +- changelog.d/x-nolink-0.change | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/x-nolink-0.change diff --git a/Podfile b/Podfile index 581fc76b0..6891bc511 100644 --- a/Podfile +++ b/Podfile @@ -16,7 +16,7 @@ use_frameworks! # - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixSDKVersion = '= 0.27.0' +$matrixSDKVersion = '= 0.27.1' # $matrixSDKVersion = :local # $matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change new file mode 100644 index 000000000..379f8c3fc --- /dev/null +++ b/changelog.d/x-nolink-0.change @@ -0,0 +1 @@ +Upgrade MatrixSDK version ([v0.27.1](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.1)). \ No newline at end of file From 4ecf7674a4d6bd1b30f483a120009a6f9fc4c904 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 29 Aug 2023 12:20:34 +0300 Subject: [PATCH 39/82] version++ --- CHANGES.md | 19 +++++++++++++++++++ changelog.d/7645.bugfix | 1 - changelog.d/7646.bugfix | 1 - changelog.d/7648.bugfix | 1 - changelog.d/7651.bugfix | 1 - changelog.d/7653.feature | 1 - changelog.d/pr-7649.bugfix | 1 - changelog.d/x-nolink-0.change | 1 - 8 files changed, 19 insertions(+), 7 deletions(-) delete mode 100644 changelog.d/7645.bugfix delete mode 100644 changelog.d/7646.bugfix delete mode 100644 changelog.d/7648.bugfix delete mode 100644 changelog.d/7651.bugfix delete mode 100644 changelog.d/7653.feature delete mode 100644 changelog.d/pr-7649.bugfix delete mode 100644 changelog.d/x-nolink-0.change diff --git a/CHANGES.md b/CHANGES.md index ee4133fae..25f3abef4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,22 @@ +## Changes in 1.11.1 (2023-08-29) + +✨ Features + +- New settings cell to manage your account through MAS if the home server allows it. ([#7653](https://github.com/vector-im/element-ios/issues/7653)) + +🙌 Improvements + +- Upgrade MatrixSDK version ([v0.27.1](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.1)). + +🐛 Bugfixes + +- Prevent mention crashes when room members are missing display names (objc interop) ([#7649](https://github.com/vector-im/element-ios/pull/7649)) +- Add email UI is hidden if the 3 pid changes capability is disabled. ([#7645](https://github.com/vector-im/element-ios/issues/7645)) +- You can now log out from other sessions using MAS on supported OIDC home servers. ([#7646](https://github.com/vector-im/element-ios/issues/7646)) +- Deactivate account is hidden for servers with OIDC auth. ([#7648](https://github.com/vector-im/element-ios/issues/7648)) +- Prevent pill crashes when room members are missing display names (objc interop) ([#7651](https://github.com/vector-im/element-ios/issues/7651)) + + ## Changes in 1.11.0 (2023-08-15) ✨ Features diff --git a/changelog.d/7645.bugfix b/changelog.d/7645.bugfix deleted file mode 100644 index b546db4f9..000000000 --- a/changelog.d/7645.bugfix +++ /dev/null @@ -1 +0,0 @@ -Add email UI is hidden if the 3 pid changes capability is disabled. \ No newline at end of file diff --git a/changelog.d/7646.bugfix b/changelog.d/7646.bugfix deleted file mode 100644 index aaae87297..000000000 --- a/changelog.d/7646.bugfix +++ /dev/null @@ -1 +0,0 @@ -You can now log out from other sessions using MAS on supported OIDC home servers. \ No newline at end of file diff --git a/changelog.d/7648.bugfix b/changelog.d/7648.bugfix deleted file mode 100644 index c8f48546c..000000000 --- a/changelog.d/7648.bugfix +++ /dev/null @@ -1 +0,0 @@ -Deactivate account is hidden for servers with OIDC auth. \ No newline at end of file diff --git a/changelog.d/7651.bugfix b/changelog.d/7651.bugfix deleted file mode 100644 index 4f97c24a7..000000000 --- a/changelog.d/7651.bugfix +++ /dev/null @@ -1 +0,0 @@ -Prevent pill crashes when room members are missing display names (objc interop) \ No newline at end of file diff --git a/changelog.d/7653.feature b/changelog.d/7653.feature deleted file mode 100644 index cabf7402b..000000000 --- a/changelog.d/7653.feature +++ /dev/null @@ -1 +0,0 @@ -New settings cell to manage your account through MAS if the home server allows it. \ No newline at end of file diff --git a/changelog.d/pr-7649.bugfix b/changelog.d/pr-7649.bugfix deleted file mode 100644 index 08e9bebec..000000000 --- a/changelog.d/pr-7649.bugfix +++ /dev/null @@ -1 +0,0 @@ -Prevent mention crashes when room members are missing display names (objc interop) \ No newline at end of file diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change deleted file mode 100644 index 379f8c3fc..000000000 --- a/changelog.d/x-nolink-0.change +++ /dev/null @@ -1 +0,0 @@ -Upgrade MatrixSDK version ([v0.27.1](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.1)). \ No newline at end of file From 3788d8d5a1730c6ce3bddad18708c6ba7e841dae Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 29 Aug 2023 13:09:52 +0300 Subject: [PATCH 40/82] finish version++ --- Podfile.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 561af4bce..2a6591aa6 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -39,9 +39,9 @@ PODS: - LoggerAPI (1.9.200): - Logging (~> 1.1) - Logging (1.4.0) - - MatrixSDK (0.27.0): - - MatrixSDK/Core (= 0.27.0) - - MatrixSDK/Core (0.27.0): + - MatrixSDK (0.27.1): + - MatrixSDK/Core (= 0.27.1) + - MatrixSDK/Core (0.27.1): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) @@ -49,7 +49,7 @@ PODS: - OLMKit (~> 3.2.5) - Realm (= 10.27.0) - SwiftyBeaver (= 1.9.5) - - MatrixSDK/JingleCallStack (0.27.0): + - MatrixSDK/JingleCallStack (0.27.1): - JitsiMeetSDKLite (= 8.1.2-lite) - MatrixSDK/Core - MatrixSDKCrypto (0.3.12) @@ -102,8 +102,8 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.27.0) - - MatrixSDK/JingleCallStack (= 0.27.0) + - MatrixSDK (= 0.27.1) + - MatrixSDK/JingleCallStack (= 0.27.1) - OLMKit - PostHog (~> 2.0.0) - ReadMoreTextView (~> 3.0.1) @@ -187,7 +187,7 @@ SPEC CHECKSUMS: libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b - MatrixSDK: 981c0e6abd40f0882e1316923edb2b3966d29513 + MatrixSDK: f6c197ca06aab29ff69d1105965a57d277dfcd9d MatrixSDKCrypto: 25929a40733b4ab54f659aaf6a730552a0a06504 OLMKit: da115f16582e47626616874e20f7bb92222c7a51 PostHog: 660ec6c9d80cec17b685e148f17f6785a88b597d @@ -208,6 +208,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: f8f66ce4fa24937192a493d3a0a5c2c2e429c8fc +PODFILE CHECKSUM: ce6afe3dea7ea9b073a7ad0406b2cc5615646746 -COCOAPODS: 1.12.1 +COCOAPODS: 1.11.3 From f68749416be37b269767cba1617bd11f95552f35 Mon Sep 17 00:00:00 2001 From: Frank Rotermund Date: Tue, 29 Aug 2023 13:16:24 +0200 Subject: [PATCH 41/82] MESSENGER-5047 version to 2.10.0 --- Config/AppVersion.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 0adc5f432..dcbbb154e 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -16,5 +16,5 @@ // // Version -MARKETING_VERSION = 2.9.0 +MARKETING_VERSION = 2.10.0 CURRENT_PROJECT_VERSION = 20220714163152 From ac94428b110a12f0e2a28c5a3ced0cac6954bacc Mon Sep 17 00:00:00 2001 From: Arnfried Griesert Date: Wed, 30 Aug 2023 12:34:58 +0000 Subject: [PATCH 42/82] =?UTF-8?q?MESSENGER-4704=20UI=20Optimierungen=20Rau?= =?UTF-8?q?m=C3=BCbersicht?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Config/BWIBuildSettings.swift | 3 ++ .../Contents.json | 15 ++++++++++ .../settings-filled.svg | 8 ++++++ Riot/Assets/de.lproj/Vector.strings | 2 +- Riot/Assets/en.lproj/Vector.strings | 2 +- .../Home/AllChats/AllChatsCoordinator.swift | 28 +++++++++++-------- .../AllChats/AllChatsViewController.swift | 9 ++++-- .../ServerDownTimeBadge.swift | 2 +- 8 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 Riot/Assets/Images.xcassets/BWI/bwi_settings_filled.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/BWI/bwi_settings_filled.imageset/settings-filled.svg diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 9df896f21..8a83f181a 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -660,5 +660,8 @@ class BWIBuildSettings: NSObject { // MARK: itunes var itunesAppLink = "" + + // MARK: itunes + var showAllChatsFilterMenu = false } diff --git a/Riot/Assets/Images.xcassets/BWI/bwi_settings_filled.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/bwi_settings_filled.imageset/Contents.json new file mode 100644 index 000000000..b76053693 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/bwi_settings_filled.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "settings-filled.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/BWI/bwi_settings_filled.imageset/settings-filled.svg b/Riot/Assets/Images.xcassets/BWI/bwi_settings_filled.imageset/settings-filled.svg new file mode 100644 index 000000000..82adbb752 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/bwi_settings_filled.imageset/settings-filled.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index d6a80382a..36706d4b3 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -2452,7 +2452,7 @@ // Mark: - All Chats -"all_chats_title" = "Alle Unterhaltungen"; +"all_chats_title" = "Unterhaltungen"; "all_chats_empty_view_title" = "%@\nsieht ein bisschen leer aus."; "all_chats_all_filter" = "Alle"; "all_chats_edit_layout_show_filters" = "Filter anzeigen"; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index e22420771..82921b0c2 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2279,7 +2279,7 @@ Tap the + to start adding people."; // MARK: - All Chats -"all_chats_title" = "All chats"; +"all_chats_title" = "Chats"; "all_chats_section_title" = "Chats"; "all_chats_edit_layout" = "Layout preferences"; "all_chats_edit_layout_recents" = "Recents"; diff --git a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift index 82d1e4fc2..85039cc73 100644 --- a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift +++ b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift @@ -388,7 +388,11 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { let avatarInsets: UIEdgeInsets = .init(top: 7, left: 7, bottom: 7, right: 7) var button: UIButton = .init(frame: view.bounds) button.imageEdgeInsets = avatarInsets - button.setImage(Asset.Images.tabPeople.image, for: .normal) + + // bwi: 4704 - ui improvements + let gearshapeImage = Asset.Images.bwiSettingsFilled.image + button.setImage(gearshapeImage, for: .normal) + button.tintColor = ThemeService.shared().theme.tintColor if BWIBuildSettings.shared.enableSideMenu { button.menu = avatarMenu @@ -427,16 +431,18 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { } private func updateAvatarButtonItem() { - MXLog.info("[AllChatsCoordinator] updating avatar button item.") - if let avatar = userAvatarViewData(from: currentMatrixSession) { - if avatarMenuView == nil { - MXLog.warning("[AllChatsCoordinator] updateAvatarButtonItem: avatarMenuView is nil.") - } - avatarMenuView?.fill(with: avatar) - avatarMenuButton?.setImage(nil, for: .normal) - } else { - avatarMenuButton?.setImage(Asset.Images.tabPeople.image, for: .normal) - } + // bwi: 4704 - ui improvements (disable view updates) + +// MXLog.info("[AllChatsCoordinator] updating avatar button item.") +// if let avatar = userAvatarViewData(from: currentMatrixSession) { +// if avatarMenuView == nil { +// MXLog.warning("[AllChatsCoordinator] updateAvatarButtonItem: avatarMenuView is nil.") +// } +// avatarMenuView?.fill(with: avatar) +// avatarMenuButton?.setImage(nil, for: .normal) +// } else { +// avatarMenuButton?.setImage(Asset.Images.tabPeople.image, for: .normal) +// } } private func showRoom(withId roomId: String, eventId: String? = nil) { diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 269b46aa5..4bfe90a31 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -147,8 +147,8 @@ class AllChatsViewController: HomeViewController { updateUI() - navigationItem.largeTitleDisplayMode = .automatic - navigationController?.navigationBar.prefersLargeTitles = true + navigationItem.largeTitleDisplayMode = .never + navigationController?.navigationBar.prefersLargeTitles = false searchController.obscuresBackgroundDuringPresentation = false searchController.searchResultsUpdater = self @@ -619,7 +619,10 @@ class AllChatsViewController: HomeViewController { } private func updateRightNavigationItem(with menu: UIMenu) { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), menu: menu) + // bwi 4704 - hide right navigation bar button + if BWIBuildSettings.shared.showAllChatsFilterMenu { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), menu: menu) + } } private lazy var spacesButton: BadgedBarButtonItem = { diff --git a/bwi/ServerMaintenance/ServerDownTimeBadge.swift b/bwi/ServerMaintenance/ServerDownTimeBadge.swift index 90e72efcf..ec2a94303 100644 --- a/bwi/ServerMaintenance/ServerDownTimeBadge.swift +++ b/bwi/ServerMaintenance/ServerDownTimeBadge.swift @@ -21,7 +21,7 @@ import UIKit @objcMembers class ServerDowntimeBadge : NSObject { func applyBadgeToButton(button: UIButton, color: UIColor) -> UIButton { - let badge = UILabel.init(frame: CGRect.init(x: 15, y: 0, width: 15, height: 15)) + let badge = UILabel.init(frame: CGRect.init(x: 20, y: 0, width: 15, height: 15)) badge.backgroundColor = color badge.clipsToBounds = true badge.layer.cornerRadius = 7 From 74ed5f6249c28f2aa68e8ed09511eb8761f762af Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Wed, 30 Aug 2023 17:55:15 +0200 Subject: [PATCH 43/82] MESSENGER 5004 update sdk version --- Podfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile b/Podfile index 67ce61163..b2226e9e7 100644 --- a/Podfile +++ b/Podfile @@ -43,7 +43,7 @@ when String # specific MatrixSDK released version $matrixSDKVersionSpec = $matrixSDKVersion end -$matrixSDKVersionSpec = { :git => 'https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk', :tag => 'v2.9.0_rc3' } +$matrixSDKVersionSpec = { :git => 'https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk', :tag => 'v0.27.1_bwi_beta' } # Method to import the MatrixSDK def import_MatrixSDK From 1955ca59d25a261589b9861f8455b498aafe4a43 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Wed, 30 Aug 2023 17:55:46 +0200 Subject: [PATCH 44/82] MESSENGER 5004 update element version string --- Config/BWIBuildSettings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 9df896f21..35195ba9a 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -509,7 +509,7 @@ class BWIBuildSettings: NSObject { var passwordIndicatorOnLogin = true // MARK: Displays the element base version on the settings screen - var elementBaseVersion = "1.10.14" + var elementBaseVersion = "1.11.1" var showElementBaseVersion = true From 56d5e3597e05d9f494f53f54f2f6ea5154c02863 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Wed, 30 Aug 2023 17:57:13 +0200 Subject: [PATCH 45/82] MESSENGER 5004 corrected version --- Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme b/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme index 28a1eae52..e1775adc4 100644 --- a/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme +++ b/Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme @@ -1,7 +1,7 @@ + LastUpgradeVersion = "1200" + version = "1.3"> From 06f94091c2a6a7dc12e3063e6a7b211fe9b8daa0 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Thu, 31 Aug 2023 12:13:03 +0200 Subject: [PATCH 46/82] MESSENGER 5004 add oidc flag --- Config/BWIBuildSettings.swift | 2 ++ .../Security/ManageSession/ManageSessionViewController.m | 3 ++- Riot/Modules/Settings/SettingsViewController.m | 5 +++-- .../Coordinator/UserSessionsFlowCoordinator.swift | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 35195ba9a..b83a9ffb1 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -661,4 +661,6 @@ class BWIBuildSettings: NSObject { // MARK: itunes var itunesAppLink = "" + // MARK: OIDC + var isOIDCEnabled = true } diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index a9abac92c..7b93bd843 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -678,7 +678,8 @@ enum { - (void)removeDevice { MXWellKnownAuthentication *authentication = self.mainSession.homeserverWellknown.authentication; - if (authentication) + if (BWIBuildSettings.shared.isOIDCEnabled && + authentication) { NSURL *logoutURL = [authentication getLogoutDeviceURLFromID:device.deviceId]; if (logoutURL) diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 05d70d765..96ebbe18c 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -504,7 +504,8 @@ ChangePasswordCoordinatorBridgePresenterDelegate> } NSString *manageAccountURL = self.mainSession.homeserverWellknown.authentication.account; - if (manageAccountURL) + if (BWIBuildSettings.shared.isOIDCEnabled && + manageAccountURL) { Section *account = [Section sectionWithTag: SECTION_TAG_ACCOUNT]; [account addRowWithTag:ACCOUNT_MANAGE_INDEX]; @@ -819,7 +820,7 @@ ChangePasswordCoordinatorBridgePresenterDelegate> } } - if (BWIBuildSettings.shared.settingsScreenAllowDeactivatingAccount && + if (BWIBuildSettings.shared.settingsScreenAllowDeactivatingAccount && !BWIBuildSettings.shared.isOIDCEnabled && BuildSettings.settingsScreenAllowDeactivatingAccount && !self.mainSession.homeserverWellknown.authentication) { Section *sectionDeactivate = [Section sectionWithTag:SECTION_TAG_DEACTIVATE_ACCOUNT]; diff --git a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift index 6ba28ad64..4c6de9815 100644 --- a/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/Coordinator/UserSessionsFlowCoordinator.swift @@ -132,7 +132,7 @@ final class UserSessionsFlowCoordinator: NSObject, Coordinator, Presentable { if sessionInfo.isCurrent { self.showLogoutConfirmationForCurrentSession() } else { - if let authentication = self.parameters.session.homeserverWellknown.authentication { + if let authentication = self.parameters.session.homeserverWellknown.authentication, BWIBuildSettings.shared.isOIDCEnabled { if let logoutURL = authentication.getLogoutDeviceURL(fromID: sessionInfo.id) { self.openDeviceLogoutRedirectURL(logoutURL) } else { From d2f239f5b2708d02f12ce768754eecb80a5b8611 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Mon, 4 Sep 2023 16:02:46 +0200 Subject: [PATCH 47/82] MESSENGER 5004 update SDK Tag --- Podfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile b/Podfile index b2226e9e7..6be9cdf9c 100644 --- a/Podfile +++ b/Podfile @@ -43,7 +43,7 @@ when String # specific MatrixSDK released version $matrixSDKVersionSpec = $matrixSDKVersion end -$matrixSDKVersionSpec = { :git => 'https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk', :tag => 'v0.27.1_bwi_beta' } +$matrixSDKVersionSpec = { :git => 'https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk', :tag => 'v2.10.0_rc2' } # Method to import the MatrixSDK def import_MatrixSDK From 71f13190ed7580c05ec209ba1320e4bc5425fa91 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Tue, 5 Sep 2023 15:10:26 +0200 Subject: [PATCH 48/82] MESSENGER 4952 support more special characters --- Riot/Utils/PasswordValidator.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Riot/Utils/PasswordValidator.swift b/Riot/Utils/PasswordValidator.swift index 59c6e15d4..cb70c880e 100644 --- a/Riot/Utils/PasswordValidator.swift +++ b/Riot/Utils/PasswordValidator.swift @@ -71,7 +71,8 @@ enum PasswordValidatorRule: CustomStringConvertible, Hashable { case .containNumber: return password.range(of: "[0-9]", options: .regularExpression) != nil case .containSymbol: - return password.range(of: "[!\"#$%&'()*+,-.:;<=>?@\\_`{|}~\\[\\]]", + // bwi: #4952 support more special characters + return password.range(of: "[!\"#$§€%&'()*+,-.:;<=>?@\\_`{|}~\\[\\]]", options: .regularExpression) != nil } } From 881d85bdba3c27a475914d5ea20c3da55545201a Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Tue, 5 Sep 2023 17:05:44 +0200 Subject: [PATCH 49/82] MESSENGER 4941 add and remove beta features --- Config/BWIBuildSettings.swift | 1 + Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift | 4 ++-- Riot/Managers/Settings/RiotSettings.swift | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 59c893fd9..b4df50f84 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -657,6 +657,7 @@ class BWIBuildSettings: NSObject { // MARK: WYSIWYG var enableLabFeatureWYSIWYG = false + var enableFeatureWYSIWYGByDefault = true // MARK: itunes var itunesAppLink = "" diff --git a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift index 023974a5f..051aead99 100644 --- a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift +++ b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift @@ -34,9 +34,9 @@ extension BWIBuildSettings { bwiLoginFlowLayout = false useRustEncryption = true bwiNotificationTimes = true - enableLabFeatureVoiceBroadcasts = true + enableLabFeatureVoiceBroadcasts = false enableNewSessionManagerByDefault = true - enableLabFeatureWYSIWYG = true + enableLabFeatureWYSIWYG = false showMaintenanceInfoMessageType = true ignoreBlockingMaintenance = true diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index 05734cc4d..f02377f34 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -192,7 +192,7 @@ final class RiotSettings: NSObject { var enableClientInformationFeature /// Flag indicating if the wysiwyg composer feature is enabled - @UserDefault(key: "enableWysiwygComposer", defaultValue: false, storage: defaults) + @UserDefault(key: "enableWysiwygComposer", defaultValue: BWIBuildSettings.shared.enableFeatureWYSIWYGByDefault, storage: defaults) var enableWysiwygComposer @UserDefault(key: "enableWysiwygTextFormatting", defaultValue: true, storage: defaults) From 6f8dcb658ad7d8bd667ec36c0167c0175bb602df Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Wed, 6 Sep 2023 13:21:23 +0200 Subject: [PATCH 50/82] MESSENGER 4941 remove old feature flags and activate WYSIWYG --- Config/AppConfiguration.swift | 4 ++++ Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift | 2 -- Riot/Modules/Settings/SettingsViewController.m | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Config/AppConfiguration.swift b/Config/AppConfiguration.swift index 2686a6048..b77f49034 100644 --- a/Config/AppConfiguration.swift +++ b/Config/AppConfiguration.swift @@ -55,6 +55,10 @@ class AppConfiguration: CommonConfiguration { // bwi: explicitly set option for key sharing MXSDKOptions.sharedInstance().enableRoomSharedHistoryOnInvite = BWIBuildSettings.shared.allowKeySharingOnRoomInvite + + // bwi: #4941 activate WYSIWYG and deactivate voice broadcast + RiotSettings.shared.enableWysiwygComposer = BWIBuildSettings.shared.enableFeatureWYSIWYGByDefault + RiotSettings.shared.enableVoiceBroadcast = false } diff --git a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift index 051aead99..0761d44e1 100644 --- a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift +++ b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift @@ -34,9 +34,7 @@ extension BWIBuildSettings { bwiLoginFlowLayout = false useRustEncryption = true bwiNotificationTimes = true - enableLabFeatureVoiceBroadcasts = false enableNewSessionManagerByDefault = true - enableLabFeatureWYSIWYG = false showMaintenanceInfoMessageType = true ignoreBlockingMaintenance = true diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 96ebbe18c..7732a4753 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -801,18 +801,21 @@ ChangePasswordCoordinatorBridgePresenterDelegate> [sectionLabs addRowWithTag:LABS_ENABLE_LIVE_LOCATION_SHARING]; */ + /* bwi: #4941 WYSIWYG is now live if (BWIBuildSettings.shared.enableLabFeatureWYSIWYG) { if (@available(iOS 15.0, *)) { [sectionLabs addRowWithTag:LABS_ENABLE_WYSIWYG_COMPOSER]; } } + */ - // bwi: disabled for our apps + /* bwi: #4941 disabled for our apps if (BWIBuildSettings.shared.enableLabFeatureVoiceBroadcasts) { [sectionLabs addRowWithTag:LABS_ENABLE_VOICE_BROADCAST]; } + */ sectionLabs.headerTitle = [VectorL10n settingsLabs]; if (sectionLabs.hasAnyRows) { From 7c78848bb29ebe2f1662464500dd36c21517ce16 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Wed, 6 Sep 2023 13:39:59 +0200 Subject: [PATCH 51/82] MESSENGER 4941 deactivate voice broadcast --- Config/AppConfiguration.swift | 2 +- Config/BWIBuildSettings.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Config/AppConfiguration.swift b/Config/AppConfiguration.swift index b77f49034..4222295bc 100644 --- a/Config/AppConfiguration.swift +++ b/Config/AppConfiguration.swift @@ -58,7 +58,7 @@ class AppConfiguration: CommonConfiguration { // bwi: #4941 activate WYSIWYG and deactivate voice broadcast RiotSettings.shared.enableWysiwygComposer = BWIBuildSettings.shared.enableFeatureWYSIWYGByDefault - RiotSettings.shared.enableVoiceBroadcast = false + RiotSettings.shared.enableVoiceBroadcast = BWIBuildSettings.shared.enableFeatureVoiceBroadcastsByDefault } diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index b4df50f84..773d6d9d5 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -654,6 +654,7 @@ class BWIBuildSettings: NSObject { // MARK: Voice Broadcast var enableLabFeatureVoiceBroadcasts = false + var enableFeatureVoiceBroadcastsByDefault = false // MARK: WYSIWYG var enableLabFeatureWYSIWYG = false From b3a625d39f9bf65d334d23e48488b12389a2ff24 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Wed, 6 Sep 2023 14:29:58 +0200 Subject: [PATCH 52/82] MESSENGER 4941 remove old feature flags --- Config/BWIBuildSettings.swift | 2 -- Riot/Modules/Settings/SettingsViewController.m | 4 ---- 2 files changed, 6 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 773d6d9d5..ec8e1fbe0 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -653,11 +653,9 @@ class BWIBuildSettings: NSObject { var accessibilityDeclarationFileEn = "" // MARK: Voice Broadcast - var enableLabFeatureVoiceBroadcasts = false var enableFeatureVoiceBroadcastsByDefault = false // MARK: WYSIWYG - var enableLabFeatureWYSIWYG = false var enableFeatureWYSIWYGByDefault = true // MARK: itunes diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 7732a4753..f1922ce57 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -802,19 +802,15 @@ ChangePasswordCoordinatorBridgePresenterDelegate> */ /* bwi: #4941 WYSIWYG is now live - if (BWIBuildSettings.shared.enableLabFeatureWYSIWYG) { if (@available(iOS 15.0, *)) { [sectionLabs addRowWithTag:LABS_ENABLE_WYSIWYG_COMPOSER]; } - } */ /* bwi: #4941 disabled for our apps - if (BWIBuildSettings.shared.enableLabFeatureVoiceBroadcasts) { [sectionLabs addRowWithTag:LABS_ENABLE_VOICE_BROADCAST]; - } */ sectionLabs.headerTitle = [VectorL10n settingsLabs]; if (sectionLabs.hasAnyRows) From 6ec9a9f7b3658ff478ffb99ee2be970437f7360f Mon Sep 17 00:00:00 2001 From: Arnfried Griesert Date: Wed, 6 Sep 2023 13:07:19 +0000 Subject: [PATCH 53/82] MESSENGER-4806 new room filter behaviour --- Config/BWIBuildSettings.swift | 3 + .../Contents.json | 12 ++ .../Filter-dark.svg | 3 + .../Contents.json | 12 ++ .../Filter-pressed-dark.svg | 11 ++ .../Contents.json | 12 ++ .../Filter-light.svg | 3 + .../Contents.json | 12 ++ .../Filter-pressed-light.svg | 11 ++ Riot/Assets/de.lproj/Bwi.strings | 1 + Riot/Assets/en.lproj/Bwi.strings | 1 + .../AllChats/AllChatsViewController.swift | 143 +++++++++++++++--- 12 files changed, 202 insertions(+), 22 deletions(-) create mode 100644 Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_off.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_off.imageset/Filter-dark.svg create mode 100644 Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_on.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_on.imageset/Filter-pressed-dark.svg create mode 100644 Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_off.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_off.imageset/Filter-light.svg create mode 100644 Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_on.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_on.imageset/Filter-pressed-light.svg diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 59c893fd9..30cd1b34e 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -663,6 +663,9 @@ class BWIBuildSettings: NSObject { // MARK: itunes var showAllChatsFilterMenu = false + + // MARK: itunes + var roomFiltersToggle = true // MARK: OIDC var isOIDCEnabled = true diff --git a/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_off.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_off.imageset/Contents.json new file mode 100644 index 000000000..67ec47a7c --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_off.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Filter-dark.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_off.imageset/Filter-dark.svg b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_off.imageset/Filter-dark.svg new file mode 100644 index 000000000..c0e66be50 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_off.imageset/Filter-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_on.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_on.imageset/Contents.json new file mode 100644 index 000000000..068ead335 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_on.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Filter-pressed-dark.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_on.imageset/Filter-pressed-dark.svg b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_on.imageset/Filter-pressed-dark.svg new file mode 100644 index 000000000..37c1861f3 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_dark_on.imageset/Filter-pressed-dark.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_off.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_off.imageset/Contents.json new file mode 100644 index 000000000..aebe7c313 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_off.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Filter-light.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_off.imageset/Filter-light.svg b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_off.imageset/Filter-light.svg new file mode 100644 index 000000000..bd03bf038 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_off.imageset/Filter-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_on.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_on.imageset/Contents.json new file mode 100644 index 000000000..2f3d3968c --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_on.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Filter-pressed-light.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_on.imageset/Filter-pressed-light.svg b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_on.imageset/Filter-pressed-light.svg new file mode 100644 index 000000000..7ec32495e --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/room_filter_toggle_light_on.imageset/Filter-pressed-light.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 15b7d1b85..70313e8dc 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -584,6 +584,7 @@ // MARK: - New Layout "all_chats_edit_layout_show_personal_notes" = "Notizenraum anzeigen"; +"all_chats_searchbar_prompt" = "Suche"; // MARK: - Permalink Scan "room_recents_scan_qr_code" = "QR Code scannen"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index 828c1d18e..6ba2911a2 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -494,6 +494,7 @@ // MARK: - New Layout "all_chats_edit_layout_show_personal_notes" = "Show personal notes"; +"all_chats_searchbar_prompt" = "Suche"; // MARK: - Permalink Scan "room_recents_scan_qr_code" = "Scan QR Code"; diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 4bfe90a31..b0fcfb848 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -117,6 +117,8 @@ class AllChatsViewController: HomeViewController { private var toolbarHeight: CGFloat = 0 + private weak var roomFilterButton: UIButton? + // MARK: - Lifecycle override func viewDidLoad() { @@ -135,9 +137,7 @@ class AllChatsViewController: HomeViewController { toolbarHeight = toolbar.frame.height emptyViewBottomAnchor = toolbar.topAnchor - - // bwi: 4179 - + if BWIBuildSettings.shared.useNewBumColors { // bwi: #4883 toolbar.tintColor = ThemeService.shared().theme.tintColor toolbar.barTintColor = ThemeService.shared().theme.backgroundColor @@ -145,15 +145,21 @@ class AllChatsViewController: HomeViewController { toolbar.tintColor = theme.colors.accent } + if BWIBuildSettings.shared.roomFiltersToggle { + updateNewFilterSearchAndToggleButton() + } + updateUI() navigationItem.largeTitleDisplayMode = .never navigationController?.navigationBar.prefersLargeTitles = false - searchController.obscuresBackgroundDuringPresentation = false - searchController.searchResultsUpdater = self - searchController.delegate = self - + if !BWIBuildSettings.shared.roomFiltersToggle { + searchController.obscuresBackgroundDuringPresentation = false + searchController.searchResultsUpdater = self + searchController.delegate = self + } + NotificationCenter.default.addObserver(self, selector: #selector(self.setupEditOptions), name: AllChatsLayoutSettingsManager.didUpdateSettings, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.updateBadgeButton), name: MXSpaceNotificationCounter.didUpdateNotificationCount, object: nil) @@ -161,6 +167,76 @@ class AllChatsViewController: HomeViewController { self.registerThemeServiceDidChangeThemeNotification() } + // bwi: 4806 + private func updateNewFilterSearchAndToggleButton() { + let customHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: recentsTableView.frame.width, height: 50)) + let searchBar = UISearchBar(frame: CGRect(x: 8, y: 0, width: customHeaderView.frame.width - 48, height: 50)) + let button = UIButton(frame: CGRect(x: customHeaderView.frame.width - 48 + 8, y: 0, width: 40, height: 50)) + + if ThemeService.shared().isCurrentThemeDark() { + button.setImage(Asset.Images.roomFilterToggleDarkOff.image, for: .normal) + button.setImage(Asset.Images.roomFilterToggleDarkOn.image, for: .selected) + } else { + button.setImage(Asset.Images.roomFilterToggleLightOff.image, for: .normal) + button.setImage(Asset.Images.roomFilterToggleLightOn.image, for: .selected) + } + let settings = AllChatsLayoutSettingsManager.shared.allChatLayoutSettings + let areFiltersVisible = !settings.filters.isEmpty + button.isSelected = !AllChatsLayoutSettingsManager.shared.allChatLayoutSettings.filters.isEmpty + button.addTarget(self, action: #selector(onFilterToggleTapped), for: .touchUpInside) + + roomFilterButton = button // keep a reference to handle theme changes + + searchBar.placeholder = BWIL10n.allChatsSearchbarPrompt + searchBar.backgroundImage = UIImage() // this hides the separator lines above and below + searchBar.delegate = self + + customHeaderView.addSubview(searchBar) + customHeaderView.addSubview(button) + + recentsTableView.tableHeaderView = customHeaderView + + // set some constraints for landscape and iPad + customHeaderView.translatesAutoresizingMaskIntoConstraints = false + let constraints = [ + searchBar.leadingAnchor.constraint(equalTo: customHeaderView.leadingAnchor), + searchBar.centerYAnchor.constraint(equalTo: button.centerYAnchor), + searchBar.widthAnchor.constraint(equalToConstant: 40), + searchBar.heightAnchor.constraint(equalToConstant: 40), + button.leadingAnchor.constraint(equalTo: searchBar.trailingAnchor), + button.trailingAnchor.constraint(equalTo: customHeaderView.trailingAnchor), + button.topAnchor.constraint(equalTo: customHeaderView.topAnchor), + button.bottomAnchor.constraint(equalTo: customHeaderView.bottomAnchor), + customHeaderView.centerXAnchor.constraint(equalTo: recentsTableView.centerXAnchor), + customHeaderView.widthAnchor.constraint(equalTo: recentsTableView.widthAnchor), + customHeaderView.topAnchor.constraint(equalTo: recentsTableView.topAnchor) + ] + NSLayoutConstraint.activate(constraints) + } + + @objc + func onSearchTextChanged(searchBar: UISearchBar) { + guard let searchText = searchBar.text, !searchText.isEmpty else { + self.dataSource?.search(withPatterns: nil) + return + } + + self.dataSource?.search(withPatterns: [searchText]) + } + + @objc + func onFilterToggleTapped() { + let settings = AllChatsLayoutSettingsManager.shared.allChatLayoutSettings + let areFiltersVisible = !settings.filters.isEmpty + let newSettings = AllChatsLayoutSettings(sections: settings.sections, + filters: areFiltersVisible ? [] : [.unreads, .favourites, .people], + sorting: settings.sorting) + AllChatsLayoutSettingsManager.shared.allChatLayoutSettings = newSettings + Analytics.shared.trackInteraction(areFiltersVisible ? .allChatsFiltersDisabled : .allChatsFiltersEnabled) + + roomFilterButton?.isSelected = !AllChatsLayoutSettingsManager.shared.allChatLayoutSettings.filters.isEmpty + } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -172,8 +248,18 @@ class AllChatsViewController: HomeViewController { self.toolbar.tintColor = theme.colors.accent } - if self.navigationItem.searchController == nil { - self.navigationItem.searchController = searchController + if BWIBuildSettings.shared.roomFiltersToggle { + if ThemeService.shared().isCurrentThemeDark() { + roomFilterButton?.setImage(Asset.Images.roomFilterToggleDarkOff.image, for: .normal) + roomFilterButton?.setImage(Asset.Images.roomFilterToggleDarkOn.image, for: .selected) + } else { + roomFilterButton?.setImage(Asset.Images.roomFilterToggleLightOff.image, for: .normal) + roomFilterButton?.setImage(Asset.Images.roomFilterToggleLightOn.image, for: .selected) + } + } else { + if self.navigationItem.searchController == nil { + self.navigationItem.searchController = searchController + } } NotificationCenter.default.addObserver(self, selector: #selector(self.spaceListDidChange), name: MXSpaceService.didInitialise, object: nil) @@ -217,7 +303,7 @@ class AllChatsViewController: HomeViewController { coordinator.animate { context in self.recentsTableView?.tableHeaderView?.layoutIfNeeded() - self.recentsTableView?.tableHeaderView = self.recentsTableView?.tableHeaderView +// self.recentsTableView?.tableHeaderView = self.recentsTableView?.tableHeaderView } } @@ -541,14 +627,16 @@ class AllChatsViewController: HomeViewController { override func shouldShowEmptyView() -> Bool { let shouldShowEmptyView = super.shouldShowEmptyView() && !AllChatsLayoutSettingsManager.shared.hasAnActiveFilter - if shouldShowEmptyView { - self.navigationItem.searchController = nil - navigationItem.largeTitleDisplayMode = .never - } else { - self.navigationItem.searchController = searchController - navigationItem.largeTitleDisplayMode = .automatic + if !BWIBuildSettings.shared.roomFiltersToggle { + if shouldShowEmptyView { + self.navigationItem.searchController = nil + navigationItem.largeTitleDisplayMode = .never + } else { + self.navigationItem.searchController = searchController + navigationItem.largeTitleDisplayMode = .automatic + } } - + return shouldShowEmptyView } @@ -589,10 +677,10 @@ class AllChatsViewController: HomeViewController { return } - tableView.tableHeaderView = tableHeadeView - tableView.tableHeaderView?.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true - tableView.tableHeaderView?.layoutIfNeeded() - tableView.tableHeaderView = self.recentsTableView?.tableHeaderView +// tableView.tableHeaderView = tableHeadeView +// tableView.tableHeaderView?.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true +// tableView.tableHeaderView?.layoutIfNeeded() +// tableView.tableHeaderView = self.recentsTableView?.tableHeaderView } @objc private func setupEditOptions() { @@ -849,7 +937,7 @@ extension AllChatsViewController: UISearchResultsUpdating { self.dataSource?.search(withPatterns: nil) return } - + self.dataSource?.search(withPatterns: [searchText]) } } @@ -862,6 +950,17 @@ extension AllChatsViewController: UISearchControllerDelegate { } } +extension AllChatsViewController { + override func searchBar(_ searchBar: UISearchBar, textDidChange: String) { + guard let searchText = searchBar.text, !searchText.isEmpty else { + self.dataSource?.search(withPatterns: nil) + return + } + + self.dataSource?.search(withPatterns: [searchText]) + } +} + // MARK: - UIAdaptivePresentationControllerDelegate extension AllChatsViewController: UIAdaptivePresentationControllerDelegate { From d27b5d8da95345cc09d3889be016247a3e773db7 Mon Sep 17 00:00:00 2001 From: Frank Rotermund Date: Thu, 7 Sep 2023 12:53:46 +0000 Subject: [PATCH 54/82] MESSENGER-4802 personal notes in favorites --- Config/BWIBuildSettings.swift | 4 +- .../BWI-noticeboard.png | Bin 218105 -> 0 bytes .../Contents.json | 2 +- .../Notizen-Avatar.svg | 4 + Riot/Assets/de.lproj/Bwi.strings | 1 + Riot/Assets/en.lproj/Bwi.strings | 1 + .../Recents/DataSources/RecentsDataSource.m | 78 +++++------------- .../RecentsDataSourceSectionType.swift | 1 - .../Home/AllChats/AllChatsCoordinator.swift | 13 ++- .../AllChats/AllChatsViewController.swift | 1 - Riot/Modules/Home/HomeViewController.m | 6 +- .../PersonalNotesDefaultService.swift | 18 +++- bwi/PersonalNotes/PersonalNotesService.swift | 2 + 13 files changed, 60 insertions(+), 71 deletions(-) delete mode 100644 Riot/Assets/Images.xcassets/PersonalNotes/personal_notes_avatar.imageset/BWI-noticeboard.png create mode 100644 Riot/Assets/Images.xcassets/PersonalNotes/personal_notes_avatar.imageset/Notizen-Avatar.svg diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 70426a25f..f4e97436a 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -136,7 +136,6 @@ class BWIBuildSettings: NSObject { var bwiAutoCreateAliasOnRoomCreation = true var bwiLocationShareButtonVisible = false - var bwiUseCustomPersonalNotesAvatar = true var bwiBetterIgnoredUsers = true var bwiSettingsShowInAppNotifications = false var bwiFilteredContextMenu = true @@ -530,6 +529,9 @@ class BWIBuildSettings: NSObject { var bwiPersonalNotesRoom = false var bwiPersonalNotesRoomLeavable = false var bwiResetPersonalNotesAccountData = false + var bwiUseCustomPersonalNotesAvatar = true + var bwiPersonalNotesRoomAsFavorite = true + var bwiShowTimelineSettings = false // MARK BWI personal state diff --git a/Riot/Assets/Images.xcassets/PersonalNotes/personal_notes_avatar.imageset/BWI-noticeboard.png b/Riot/Assets/Images.xcassets/PersonalNotes/personal_notes_avatar.imageset/BWI-noticeboard.png deleted file mode 100644 index 7d993a44d86df2089fa56a8cc5b247ae726f4a9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 218105 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 z zU0LEK@EX0qMhw8`)OiEephm-w^-HpE(`PaZ&Lrwx@RGLLegs%_;9n#!HvQzYq}#f# zn%F5ayIiH;KMGo+7ha*z4AofuBxYjdEMM85Z-9ksUU!d~`(0N-R2zxvgREL0Ob+0x z=0khr6jk=;qiNDt#M!+&{F-P*k>TpQG(z;fK0ZxKqt(+_kH0?F-ZdD#^5Sj~@Y4So zBZebo?NXJF&bdTXHm3{=E9fwf*SZ#^VA@mUiFo(k?o}T*Irf&B5|H@P6KlU?ghq?G zMxnBlSpJlntlaynmIO3M7f_XNFxv`ItF2Rbdom4L30IE6ykKix;P~Io$;g|sk!OKh`F*bIIxX563$?u2O@wOD-Z8**JN^C6H6KNAC` z*T^2B0{_Z$4D{$WBJ*lqE!wwqoQjFBFo1pf>r&e&+ zVxQvymD5MNRPkM1Z-!*Gk1qXbyB;qIgzMjY>h9n3)@^+ooN19ttC?#}^6YN?L|z(P zykoORft$U2=|_kO16!kz3+(58Qz$XbsZ9ZYXLc8l*i)F&8FOtjK7_M`AA{62)do-X zD}Zk8iW2+BNYvJLxo*ki!|f4lV>x(pF=_+KDo6JJ&h~7qD~M z{|&BBWsFRD+{lNHA|MjyJ!++YwyDC*|6iUtYLqK4sn{$=R-ImVC9 z>P;CPhZzLDMg!@~2U=GO>q--gm4Gzv!A{!v><-{2ReLS{9Sq6HT& z@HjS?s&x)kn_(O(;>15oDBqiKhfnMs#O0p01S;>F`LOlhtmWK%^Xy`gau}QaHSEoC z%=1LVe;qn{Qf>kmty#B>Y>Ho*uVqjx7W-PH)7oy_=oj{wkbU+W<~<8;gP9-j4Do8F z2F|oX`qlZrtMSpSW)?lpzcLLpIE-^83ol$WECBoAsqqC^T?@}zx0u8TO3fJ9;f%{BUd7>0h7C1oZ7}}QmzC zmP%W`Ix^nC`Gh3=9%-f}Z^MNX5M$2d!ce<(8*P^f0$hw1AD*K8Wat1uW@QJcv35HP zp%X@M%ExWLaa&btSEZqnR6PiMiQqfLX=b_;q1``hM($$X#hONbT|{QvTF-QD>1fF2IBmh0>zZU zvL_FFaOG~OB(}?b2P=opHp%U@Lng{yK?`Flq^_hiCA1S*W^sp;{rXsz%-$nEC{&hp zbL}WgYdwRxh%z41h7e#g6>`bn#)5HfeGNb|L*aY*(0}T`9l?_wD5edW5Z8!;>efjo zQ0(sAL1*wPkeb7&33(o9N%xfB`1`qBII1q{RFt_woFVmiGko7Bd|u?_=OBNc4=xOH zYI#yZlBc;^aA$l|D-5gba7y9}=ON?OBlT(z^RW=IrpRy9l7Kq$UkBu<+$dWb9~vJf zui#Xf{9-ysxw~Exx^pv)VU7&q@3bh$S^-9TTD^&3h^a6tP$SONmHu`3Q1)JeBIIvB z{e-1U%kf~CO#Pqw2i4D+r)`eF4EYhn%g1aSco<+=y;|Y~jPIPW7g9vuyqyhDRt+IY zTz$v^Rjc<4nNbY53u&&bpCxu?t`S<)un=?%4LSCpwXfSU=MLOizVWBv>Nb8Fs!FUT zgRVxPu%|rtRzY{6~@qejwRqXzg zw+#5Gmp=#&is5dt5$p7|f=ie3#Bip#sYg~`E=qM z=yCs_Iy=umuw@$4TSn(Cb14M%Z8HJOl%0ic2q&8iv%z8iG<2hwn%mG4ruoc*p6OMS5i?7s zA0K-3y-;CM_-~ZQ5;2xy#TaiRze`xvZXSKLZV*X2{~Pgf=|OULi#8W=x5-c?=`Jxq z6ufL8jd*cclwQ{ni4d7n%lvysVDrY7KORPAXh6QC?t-Z^WRsr%dmS{%bLWq81Gkb= zA^PJ#WmICoO|cb}f;@7e;^}NvLGJ)Ki!xF}GR4frbP9Ll10?l1xZd|cexFCUHuyp^ z(gXuY&|4c{6b#sD^jm!1r+Nqz&S*ISAx9XlqP!};#y~8^4z3>1&x34ZdJn=SfNB)Z ziFx$sA)RkPZwq{g9dK0sFFl0E^gP*A=0m%RIr#C;P1g#pf#PM#R4YIH4`O#sYH_`q zJN}=P&W~TSNl#*8v~~tEFPsvn*($joBMT?q{T!Y?xOt@4NQY-93}HaHY;H|AJ5XPV zXm$0Kjv7Id$_?eLa8e({l7BNSHvfZ{kk*_t==d9AO1Y z&mc^iMW=@K#}&6m&aN?2mAk7k9F*~r0RCsFP`R0W0eF1d zLM}6LKCyY|;)Q_M<7>R|URaS3!FH!FZJ%qGC(5bb+K)y2jTa{izy8UGn2 zwH{Lf)zL&g{vX%oEFV(!;Tm)Q6WS%vSEL-#25! zj*V;Q%;q+#WAWH=^z5P6l_6dZp=R$0mCo@BwFt;qVE@O)HZ=M;JD)tr)*6`<93}R2 z<19QarGde$cnXETDO$qfCW_qWL$U%NRnt3AYA0g9RmR*#I~_)=OCCzS2dOphvPcT- zt%tfSO=L25LJN?|HwW*9bX@Ge+5VPoRPXO2EJYEnU4FHRVq8_Sw}@9p_ah>SEbKaPp}=~0bJmteaPDBbn*;+^hr@0>^<;~y8V^><`u zUi>#4PoD5B*&Bz0J54b?PsdDAJpXm%fX{2%kCp4_fmOwK^aknwGM^Qb$TEJwW~(oY zxd}$SXG{`%{g@b#E$`dc@Aq?u%O;lHUIP{`+sju<9i>i8IY))w*in#Xp3w4NIu}5H zdp6`raT-g!x9dEkC)=W73pLv+r~#p%MRcRb@F>*H7_RzT2*B$ASJiO%9w!;1J5Uco zMd1)@tF$KS>F(phy}PWn|0IEj3?ictr5>)7O&fMG`kQ&BZ*SV)a*+scmO*FNNvXA6 z#@+MfH7@pUYc~>TAeG_oc^M4^Y^Iop)d$ENlQjRlsT9CtFUNDcmaKe z)qCH>fA=1DnECy;XCYD(+}gKM;lwTodz7q$Y%*gzg$g^Lg`sD)U>$-a=@WC zr;c8(|G~mCbil}-&Gp?LbC4~jr*n!6vACt7Qe7@&o0GVQeEK48Few^zlA}T?Yk=w8 z3QG~aqdWl^asU}h=rP2n!(fN7vjFI*j0SWtX6^k+ih+M`sU&*J8)2y^AD|x#t+@_= zDMN#cbQRoPz?sPkPr7ta`{?dI85EMh+?L_{^G|Iu^#%d*0*ZD?NptWTgS|K)I=}P}q@ncX2>!{F zj4@E~907Ia@8Xbi#jg*g@9#9AZQgL*mE%z9^OT0^S>2534KJ`uE$JJ&eh&E8zHz?p z1^tT29+OFTOjEvxn!vz|!7Uq8UjJRV!uZRs3%vEa5C&%&8x>>s^Ti4*J)G4k2^?*g zrF;tAD}W?o-Cgz_Kl-#eH0tc&t_5A8Qw7r{N~2Z>U-eJ3{|tJJeLF%kC$m~)1+xoF zvh_QpDyDKv8d#!68kZyCfs3H~SnbW+#KhFjWO_j0kk z4SYYrVbjSI)+oWHVj(!p)ss&BWjjd@jldt>H*iN?`NkmdB6ifHO13{Ao^C#PSy5)bm(KvJooxr>M?X@>OEof<+~ z1(Qn19FYGY^bGjYPleZLvp`tuC}^ou|Dz$)D#o-YtcaVM&0EmBH_B5Nw0a~j1|sXo z>F1I#In0tZ_#4_I-QksRQF=0!n($_SHCZ4x^yVVsI1KDqplZXpm&5T9;@tiD8)Co! z{_XlN5VgL7pLaUye|l138s5uz^AH27Z&Etq&oULt5!{C(Z0nWcJ(wacq^!kNN{s3V zw2fw%TW#ORdjEk{Cd{s>dE~r9t-1O<3HTbo>cXg6M#W5Zf@|&`+fzhlj1R#IlOe`5 z1+8o}{ZY2_a3QlYKEA~hmT{$9w}G-Xdc6!|w+gD#8-2e@O8<~EDaTx52m#%CpwBL5 zA!>2x24hntT@un1a_(ozEp0Gk!Q38@>*#-eR_jXAR*oVs@#l3+L8&471`pLhnt4SN zbG(Slt$&hbD)fLG0k%e)H{D;WLE4Z1&(Ghtw@0cMm6_=Fg@SlpF1>ZDRtthAD8rZrB^RQU*F;_VzxvF z>KQ`WbaKI)Zwto{6?GwZ#w}L+i2Ql*F*ju1Hx_FYs!sXzsG5qpM-CCA1*({&>>x=0 zd+dA%S60MTIwlHPN4zKts39cEIBY$+weVSVM4n zbVL9jdjy*dk+u8pbPN)7gh6pk`a3V{GH<+;s2MfkYV;5zQ-1`7!#{U4Y_2WeNV+>! zgQXnS#cm?l_Qv-ai;I+)(P-ucNC*ubJl|o(MeE^`dcW(aNYRAPQP)wI^6hX2!ul=b zT49}0R|D~A8Ux>DZaVz$IJo|%X(+#U1P4m@HRE=0nc^_gcACboQUg~40q(~1*wYa? z{L|#8ea3=!!kx+8`SD`k2*by4TaZ*+yNu&wKi?}NLwv@&$-jFBdeTQD%Lt`bRs#b@ zrZDwKNG-lwxXxgB&3MqN*AEQRPR!*uteoWT0 z0w!*!55cp7#T-)iYXvfzgk$HwSyxnfD+_Exncnck=_NqmYxi;_t+*34`rbF4<+p5b z8P9=A^fgHb&o76>0mmIP)Wvg+4r9!+;URlK{VJ53q9Ej1|JGq^7qU0hDzhD&X@76p zy*9^D$41umi#gqO$>0KjSexCZ6rn)R9!d--W1{8re*e`Kf)DOMMonj-tpZIPG31Nx zvc-bSd3aPxzyTM?#R0`Xb;HWJ(XtTp(X;(2m}iSnXOy_+nq@~D4TaE=7c5F-VfjzLE7(`@{63h%yRqo`!Gs%FYXFZ zIsva{{W#i{@f6s-xc^sH6H#p`Kf*5_J*M&N^IeaF*qZ9h<}Rk!nR5z9-pp=y$eZ4V zMol#ECpT-VGHm~-q=ygDm=lfZESj&V$kC|96D9(mAe*c*2V`QvpG$bNf=ZJy$e>0% zAv^H=gXI|JWfSDKf504$PUz_^62344H~P0+iuSejs+Z?F-~DT`vrW!G6ld`X)a<~0 zdWZ&AouPNlW;pQ3w*l9C5nBl@D;x2*%%l=`dq3aZu)35XrE`ioL?=IjfojK@Pz*wA zwbqpq35V?HMEXojZ+ExM+GS&NR9i*I*IZDiAP)?zB!KOrgW_2KWGoR!!7n!A8F~RU z7be1);U~x5|BzhZdk7#;m1rZ}TFZT*0^~b9CD9zR)>%L>o3)~}q1U?KNTsHK?rkv` z`?&KHxEP}xbVHxGt&zHWE9=)duDL`e`|J_bPuv3}IGx2Vo%BHg^F0f-sIc!z1d!j? zJs}Wy!ubx}{{YigzdrNmk?IQ6m@g20eSlPwEv~sobeVg60mW|%dU|*Y2kiVmuD-+{ zs_=dLjD5*gmh4KUQYb>U83~n82_Z&BDr@#_%pjCqM6wK_ED2@bW>ivH#!j}e@B2Dt zJMYo=`}=*~_x%TE&Uw!BT=#Wf_x)Tq667{S?O2e>L<(!Uf}h4$>cI6`Gjop{hSff;=SAKWu2?Zi6$psjbtuMESrl)uO*Qb&J9#D5J- z#iEi69S8Dd-1?z#xW`5}+wLZ#6IP=q7DTsc;0iof1-&@8P%KH_fGB<7UCejJa3e9{ z+yp*)(4oHo06!5`!byK6jXA>%++?*-CB^rekQ=%Q zmY;Lz85v<5B6bsuUM>|SLuz5!w1a09e}Eg>yBq*d;6$Z0k5VjXE=jd^Uu-2wlR^a! z%MX^|LbSRx+i%%3C)L0@F#V@|qmFCf?A(**llR1!@j`S*({LjR@$ytOHxL~cH!KAV zgEgbF-+_yMNVK)ot>jh@n|q>|2JVCGX^a`sDWnl3A(56fV>R$_7AKGa*i{bm;7`wC zh-RnG`~>c!7<3>rnSZONK9@DPXE7glsfS`cpTY|}W9F4AL0WbwaHuD}57=gt*eo$` z81us~2rqK+1g#Wp&nCv1Xm0Wqvg%#o!SbQp&!{B*X5~JX4I;)HBw{EVMIre_s}&ChWM4p(%Yho zqs7e7da05i|9MD+r-v;NP@hX8sC6bblmyA_1$~M4I-;CPcn8Z%skHCsMZ~r2biw)g zeAG*l&_qd;ZOSseHtJj=|I@1wIZ!-^t~ab1w#zkC0jaG|B%a-m>J5VhqUli#)n|(* zu80i0tqBT?9aYhV{u6i-@s+f8zy4;`W5|q%_;LQ(=4KW|q5l`+-TFYAKE8YJ(sS2P zm407H8n<6yYH@3}*+{a02pE2ms{rwXB?Qc+N2Y@Gq3%tp8^L-gbw{t}S`` z`t}Zky40#GKA2nm=q3Ga!f54%V@KTP0XjQGU2O)BOyZ!4cRmIM1|N#aJWF8Zen%YbXxBoI+4NEnO^j*W zA(QYo=nPqA)Y{(9WhZ4Bp0flbg}{Jim4iRsYc>_ zMv_*-U{_W^yToc9E1@j!xlOwIKfD1hFNumOsaGND*rA3={i@%~+fnk}_*Qung72*G zqCizal@8td!x;0C#~qn@K4|jCh5V{L4*({+b^MlQvBOs+=BhwcR8k4MMlKx z8_){igL17PpQmn~Of6j*C>&>$CZcH24)OEs*8Q-w@Kl6Eor4ZB#8DuS=Gb9b-2=Pj z>F+CczP2MHsT-lgNf+y}y$s`0nAJ7tB4B;L^@AM^fSUut5k9}{RF|a%&hd$f3zv)q z7)ExRZ*kG|YlAS?#bNCi4i)!oPHGq;aaUT5-}6dAIMUGSU9{%0n<;6mwR;+3r>f2= z6US2gaKi}9!71L-f}TsuOR_dqXprLS1{X1R5@iyP{?ld;P~@vZTCf z$*4f}q@DFr+NZypP%`(t>W?EIxt0Y}&9HncTI&|T3aDN?AR|eI^j2mg&L9h8CzG5+ znGA2JJ4}SbO;n!;tQ`t=VZ!`TMIzh|`}*J!9??6=*#<(5Q3v}5qX~=h7u++NmcN{h z7V6u>?R5nB@B_9Ie(Q%&Yu^H_EPZ}O1q|-(vDEC~AU=>^>mBpIse*!;d@(3g;B!Ba z3<=K##$DC&>mMm3o}sn9Y}=KA9pI?-vhfG6KJZQ#+?!vD4d6b=)A;;;Sj=A7Z@-2- z%EJ|NXQ}f+VH%5(!0Z;Y8$}v+bq}|OxOMs%zV4G@JJ{Lg<#|9%O6xqouI-UkIopH& zy5OuOR8id=B%iSWI6vbH6xuaWi2Aiev`K3{PyRMx@OwX6jyzCg`)zYNcV$n$AAN={ z0|S1Omu|udNAxj*^k|p8MJISTjggh_0Q8I?Yc8Hw?}j)RI*&Rl3G>w5WQLs`8hj`K zn9dVg4sF7KZOc{FH(m$p1*kWBuTTPBnaIQqN2JR`t%aha^hu{L6>mXu3AE||hr93U z1uPy)`IMeLwy}bvro8G%XP-odgNaV)-$R6J&FBS=EXfc-Q0)v0UK<8U$0sD&5x-hA z`^-+uF;!oLJL{s03cI_2N>888M_|oBMkJevmz-fc0@=EY_J=2SXvWg z0~=yID|%PtWiNu`JPOl9aXgJKM8JMzH(fXWx3xFfxJCX^#c@gVjJ~a-v`~qX0ZM#Bt8=n(*fX7*XOLkEbEbCnn9fJ;TrCq<@ z#FL)9@GlzBp+BDiCGZZ_R~tfUTpqpZjk~hAhs|11^o@<@X|%Z@@HJHPT-_>>1zSVA zzQbk-H^=M+wV@Udi02SGxdBZEOt}2(5fwvLSa@9KdJZr@0idpW7O?=A4r2`V>K*{g z*MPIiw`xw>8M3r*)Nlg~(DQWSmUSs~GsF%`>e_JBusI_^i*pGS$M>VvP-=GciXBLm zv0Pqq!fn$Cl2nxh`&v}=GjLog6rz83M7SVESQ4ta*qJ5DZu@A|9;`qW>LBT{Ei!%1 z_!r%n-3lg#^cwuies|a!AG9Gt2|-`h9HADBq|>G}*M{xGrtLWn8e4~UB8Pj2kAZR~ zYa#4C4utMzEbZ^C&!uOI;GG?8Gu(kJY`t#$g;E2hi?e?QA4a!-xle_fT-|O#3K7p4 z0Yzv^j5qT<{f_cCH8pL~B3Ug8@e9F|Rp_cIJD!97C*SWU$vt=#XIAt+bt#1%0^z2I z5QDOqHn^_hSL7fT$WqU;-2o^=JlvotOt-$78sE>No z{*ok2s${ohv^ehy+ArlEz?5SlA;#|Gown0{t6T!IDI4=iUIl!?-7A&Z76w9gpC;~*98Xi4LS;ON`c7~X}GrE8%9+P^J+>&QuxV~AOq8APaSHuK=xPd2v}?@KIWIE)(fB&8|P1$^3C1qo`Z zSk^s3ai!4E^exTS{=MONbkrQ7=eFxURJ@J~%a%2em&_@tNnA2+J+T4Ib_T zCFaZ!y=2AcqOsGh+O?!&Mg`@Bq5PzVKTdyH@Jm0ub^zeBEMkNUx+^$vFwVDJ8v}KO zK@1V`W5nj8{|7#j;?`aHOYgy+i;S}hMqPKAlBgf& zTgC&8MnTVCjl{2f6f~z{Gx9P!jr&1k^t-l&o@^*E(B!#xQp`yXo@H=naG-VP>jx8U z#DCD@4Eb@M{7XM`K>Ykn%da1(u}w(|Sm!XrqIN5u<xub? zt>oa5MuG)RosT>^SnWCWnE_~v=)ivc4veKY>dF>5>i$zoRbM830wMA6zj6e+{G9u~6hIz>15DiLqd6YQ|nI4ij;+RUyY74)B zeahm%DGk%HE!VTeV0<|it0}_|wSR~qDjkVyC%t$gN#f5|x#Bd*Sy0pHJK_%d6HtuZ?%wl=MUYgENJKK4pRr&qYsp5N(F5!z#1>#1pk&i$kpG4 zj_2v@(j!3;;D;ssFBuB{2BLR;VDS-c4mX-GcYaD%qC274oKSx$+f$n#r>YEpHzx~= zY2JXOS4Oiobw#khh4B*=u4hU!ZiOlrLhEG?F8A|h@|Z=0#bqEcC|VAOQo^S(Ts`Bg z62}2DvqZ(i=TB?+C&8TPc1?Q7592q`N2_ zFhVA_9s|er3$Pr}#e~Nb@^Ko&@qvJ>KFq0QRuV1HR>{UrVT7z#}zdt zT<{8Z{`(O1M*dK>^I0KSDI=rVhx6X!KhU~Mq7@mLjox7rzT3|k08D`4L!v|Wnl9)9 zc>{z8Q)Hf!ptEQS88(bdO}=P+Ni$V%Po)>*n}SsueS)^7YjDx6mC$tDCP`~QrR zuhowmKL%Dl>Re`tb5wx9yaGwiv!!9*zGeG{6$ff3aUJ9(e;`_LXFre)1q^8eKzTwP z;2CBbBq4&0&H>7ExiM>@750o6Zn?MXYLYCa?y_A74{lLI%=l#38gxY%7&P5|k3tu@ zeL#=XAN9Tyb7cbAxWq)ERGpQ@ndnf4c652rdIcxC%u(&TKa~8;YA|RPa$qJN+E+1b zmvwiUsG0g zyZq*F=mSS^n>3WIE&}EErSL4wSG>UX^%m&?XGCS$1fY2wqd5x{4=j1x9~_3|6O^V# zWJp+$<0hNwRLr>R4=v-W*z7uf)uT>*jYI=8j^Gz}aOkZeN2T>|oV|+GL^SpuC{JG| zbo)w^YY0&PQZ5!}M-{eaQdncbPhuzhx*zxH+|wa3-eQ6Bf>Twbr2+Es!tSTkf7jm zuk&ju&V&*qFFY1W!$Eqp4bOhd8!!N<%1qApDP2Lw`nL?Acg&0XuwQA=MVY2x;0(28 z{c8sak|n)nkv1s^1pvceew6&SejO`Fzxi8ON@%<(f!(t3a~K`W<_4U17}E6EBRhis z5UO&3yWWHpxxf^N4HYdrQi7zHSydAuSXad?C!uiWkeW-c^sDv#f}(}VZ^!K5QgrBL zJH(rY7EMsDCcGIZ4s`b?0f_6=t&5|2=WEQio&p%ggSEijT8wg#quQr3!B|>iYu};A zV9+qB?1!MY9)rSfxEx!SKC42bF=mCMb6HFp3d(BFZ!J^OM%VYvQJA2i(=e&L(@{uRk4HCfoN7zgG)M_roqJNx2cDzO9P39v zPkWYzE;uES!%1H_RwSJ9g8|5eLI^`(S<)i7ubb4RdoMip=AEM)3Wr$d#R01Uuy7u` z1^jpbn=+cbFrv21;7iq!lm-y!FOWi$-d9ygyX`RL&~>mZY|AGpbngdca6iwH$Zkrk zak(i-Pz2M9CFd)4aFS%2u~BysZreb47^12{>*ePaiVNwEtU%5RO`}-<<6-xKvrZu3 z$xW7-AEi$d@RE)zaqN#!_PB2FZhV29R7*!%dwCu8$^0SXEdirXgk>|)Q&aa7dXF4w=%VI&TGCFPtBG0qi z3%6FV*gS5;(`xwd?WKhLqpAI?cUj(mut6w&!%VZJSbf;EcYm(s>HCtKC#HMx=lxRa z+j&cXQ;)alXZHCIB3-V^ix69S0WoG(H>fchKlTVdw3n(A!SbopAz|%D;}Nn3T75dz z&gf-%CL|0OX`a2-W`X`pCQ7R^^%Oa2vhvA6RDOFSuBTm+1XEMK@Sqv&%F!=gpL;62 zYCM!WG=vo4xYhr)>8L#|WuTPK*m)4*#A=>=*}VCY@C;Qo9z0q9SjGZn%l%l4sV zq+ygLrYNQTHGaQyiX+Uf>jN+nciu3mgE}Z!n0Ana{RKo zL9u*Lg1;W_-T}Eg5VxH3pqlJKBZVw9;xLl_n->F(lawQGJ)o=YP@qa8W;JVu;V|jp z*(Rt=v&3*N@VS8szam;yr7}fs+>OMv_G44STtQ&EVRUPHvg#_aRCuc+M zNXYMHdGE@+j}GLFd0~ACXQVmREHs*^CEG7u$qIf$>6++P0!6 zNIkym(ImgpUN49FY`_duLxL-iTu;WewL(R~{m>l!o^ebeV`3crYuW0C4TsdDoitInJ`H zj{2uox02CVn(K81%xeB21RKNM@SB#5oht*?tIYN2RTc~caCMvq>!4{%TsvPws&dYT zDtrB$@=!BO5Qw_71Aa56pAdKcp=C~$+#rYnoz6G(LV(vZ2iyaBf-Q>~C-MHBHVzF` za$CZ)?m>8Kl83Ys6@w;P4k>*|i*j#hT!pUVr~$(#esY#?rg*&P<;{;Sg4`FOytbnh zxk5QNZ>=l|5kZAu6xtzSH|`$YnUW8|DV!|5DEwb;ou+pn`R>9Y$rr2Pm8`W~L^XgK ziRfE8L&=uo27nYK^mLknTTbvVAmGFW9mAP7pm5E`f<1ZiT-Zy z!pi;8?;HIf)}Cg)0f*9@!P0LmxM#@?`zx-80ox@pCW;m(ijM{*cA)^Idquq80c+;+ zcQ#vl6*K+9{hOT^Gfc6Nyy`D-)3>U|48751()&!Sd$=`9Nli_JgT68M&Vjn4v$sF6 z(moQy(Zqw#2o>aiQulT-0k8|Am;ME3A8M&8|NXQc>kha{;YY8wiAOW>@|qxgy}e-7 zX)xzr&!yQ*meAetgf580T>Xb$3>5m9ZWQ`@NN-P%()#k(UPZFqFI>%pHW>@2DpXKQ zJEJ^$uG8}Ce_=n9T=!D~^^-S8tG2jUgDTGjl zL-L8AVfkceIUpi@`i=-Yf|YUlp{Dz`uNZsQNgWvZ+G?d9L#J%5EoM|Bi8cNmLz&gL zW=-e} zzl%q0lS5^m;v+qVpC&?msw@|<=HvlxKu?I7-`xiggDjmipM(W7E$kuR$rH#A1AU(; z#WIbH17d8YB0;UtPPuS;N!fNXSKOlXFWPWPsZ;Dx!&uhdZ8lypg&VuH7{{vJuudrL z7PGS@!mH;C!RXY~Y#k)^_)atwKnQW%T?3hcS$sQ&OL1ulhFPzM6I-{wYWErlnK-Yd zI5o;qWww|zk{-tIvyQJK0N}*SiW_pBez~Ts4A7dWLL!u7krr^PMh^BvXjQ#b-I7)k z0+@<1cD@~w_^$K-ShZ62VAO$y(Eb-H0-PQI)RY)U*#{8x=B=xw3nPgVWK@InGLim}(?3-+7=5lGUCr21gubEeKrg zPPW@r{*Wot!v-mIds!!0&yi1^esQDv?=4KXkHLK)^bw>{`B($X%#A0>XxT6zkPiWg zrpmu>0+T>~As2<*-(GZ-9at9rcXEzH9x-HS|2UuI*#%FT5dGOY#(^;uO%b+ccq%1<=^?+a<#phmXeZ1DG)gl%MGARs30bei0ehsunnX z@5}xBK%+M(csqmkH34`ig>mJOyP|;4fhqIN8n~iUB*66+Hq3!c0_;jaP*NQ4Kwqg( z6&9KI!l6G?iHxLkDgQ%Bapd5wm|L!Y$u~#59l1)adbmdKSKYsWPjy_gqTpWopWhW8 zBZr5`C9y2=k-n21yMBrRux-H{M0(a`%D7QiD%!8|dW_bW14M;a(mimXP9;shlIuwt#!FZ9 zjpyG_^Fz&5qZ_Wo4-*p;%4L#6K8qFcK8CswJ+5Od){c)CvieQ2{#`D|;5+|6TGd|# z4g?#&lg9~6KFKr%W0%cwRjDUstmzsszWR@4K&7*+fhQ3;?F#TcvH!cfq2cSIA~Y$b zB!qnpt9nB|a2%#)7lHWI6#5y~N!WjF2V#=`$p;?!|1glzLT7Z@p0QKm`&m4LI z2O1b5wAVYc`Of^2Ko#bGIjepgWSD+`h z@E8hj!0LJE!(@Cop>f#?`QR7M`8$mcZs#~W&9ZM*>^ZhtMBv7JSa5i^3~9HrOKj^F zw8v?XHR@0TkSkuWbp67nPZu+6i=mH=0q7KksD{-CB{kr?(9-wskL;ZH-)v6}tzHAj zsuxdsV+P|8tWv(c!3&NM%}JVA*5pK`92kBZCR3`alU9x4yJ?%5(#WOQjXZ+-mV^j55xv050nCN^xZ)BH{!}8=K!bTqI z9^1>(Wk0;l+|S>=4mnGBGLFwRmA#T8S)a%FK2RB!W8&?|qLHQIx*v!kghx^JHL!R8uWR9M*r6>hrC|$Wk>94H6-lru&(8(NT^+p zco#mH-H&PA_UIo|>G(9R(kOut(51FJu0^1xt#9O2mj{K9v)ax<=*E9W_x}F-wgd~> zyUT$jg&3pE?Md9s5^yb8XvpjszOv8mE(RLYhkM}0`jyMvc^6Xqj{}`D{}^-V*S+*+ zhlB&U%rWT)__ICD(--RM2$;TJx*hlnCnI^t6kORtvdpe+W<4s54sTWyOp@#0KaUvMU(+BeVjwN0gUU7IMH`e5wVn?Hu+oPs7+x6=RINbEyyT0nQrnzXL6 zPFvjX{z}+AXN4Px_=m>dSS4~>Wn`XWWT{o@j0g_e-$r`{m~k^2(v_Wtmbt~l?*fI82C0f0tS2IpWoFVYisDE4OOA~kxAB`bu*YL&sZ z`!za9)MQ@BHs8DX@w+({9L;cuT`fjO_Sy-nk~CaL)xun4C1teHRgu!J1RVk?7*xz) z{o!qcTC(c(pJ4t6(fUnHg7kpOMerA|Cip`bl5L|}A?$EKgcA{rWH~#4#BGX$ z)(?MH44<8PeU;JFQ1lI_+erPUgmd70PRFZ>u1i3!9!7#0Dy9W=eql~Ko-lN07MZ;D z$jx_Ths482Ocr6cRlf!Uz-~C?Bw(`|{_a9dz9)=Bf_pt!UvjN>z-aQm{F1WuP>P)0 zk&ZD|qwPimYnB@nB0)u?W4%OU@0m0`z^-`YSR~|XmIzIK!~@V``)pFG8)Q^M8v0rb zLDa=*qDLI|1GD$Hgrd%p7VnGG!{@p`9OKXi96{HyB(E3b%88Nl181~f8yw%BaXoP@ z<4m0~69X`R`N>Aeam!E7R4{rkaJ|4$G3@#SqM4OA=JZ%q$n|!HWBZb>m$k;6q`P;3 zzX`x5fDs(co8VIayS=rel)=D~rv=swLiBL|vX^D0r0oWdDnlIfNQV52nzRr6>W0pY z##s^I#>FYf51AWnioLQfCTpL_hj{i6O5b@?`t$oT66Fsk6b1rC*1yA5pgTego&Fjz zY^Vt5Jz=3kFL5>=+NJTcKd z`0Vd@UGozF>ZUV4!@A4_I5Bl+M#d5MOE-=6T^d1e#U)KhzWzFPB;*oWRuH6mbPm6K zDPK(d&DB{z#4Q}=`|GL=EDWPdpkOOz$Nd_Ge*b$>XFhZk@mlcl<8YQ;D1!~CAfh!d z>8gM^IPE?a>`Z-Z`)*bB56LPTi)E#u*}h%0^U?;oSoNLBOax1L+@H&=gRs)Z#?0~* zwF;bC-#vx{>M9<3Jnz}=W_*z4=DYLvD~<0R5zx}`Pl9B#k>)Y|pn|1K>O6;h%aEd? za^5Yj>gvc7@ewIck9YU6w3D)d3R?m(E=!kcc8;FsyeZoA=Z{Fk?r7D#x@&{Jic3f5 z=8gJ1BAR$X?XQNGJN>!;e6Y#y-8?d+EbO*&t?driDe_N&-^Oq(*WImsGRL3C1zy#A zZ1&i+UwY})6#=$eccOBC37)*2^iIUvSV()ZOg8dvRqaXOzSZz0>0$pi@1fkv3R&7B%L0OL_P!*Bf!e!ZyFdAL8y z@B)6MTI`Zsno=;EZ@EURqQR4fMg@f{Km~C{RB~`>M_^?5ygbnXGdv&c0Cyqbs5y@7 zIVxjjy6lPDnqGI$o=8o6IBO>CGMX7ypKl)Stkse@nZREv*;@pGN4X(Ffh#keZ^=KkhKOG3+6?_h` z)lKR({+^c!eOCmq7)<>X{WJs3mB(MLSzyV0;*n|ha8c}8gBfGIp%|=>>sl{@VOK5M z1b<@_mMrx6!6RpZ8;_>A!qq2l?CpP*e>m=Oyt)gl2|G#h0VkNNtLooJ)BTGdrvnv% zqB=?Ksr@gvI0Z;(Z~^&kd6bz{f}Y{5+1j6W8GX-iI$e|dP24J;CbfymPT|{cloHce ztuCI06*Ngid?riYBICFw*v`|4`wc{s@cs4m(_)(549Wv3rlz1+r*SJ|7TF88StXPr{u1A!Vsg+fBGgbCH&E?^P1AErXIm=oE3(O;jde3vCqGjV~p&_N7#=B`qtW?#r)a}vx%6dJegkl)%a_Vc_M40k91vG zC3}5z?TW%@o4z=N(-qaylBSC<9#QttlJCDdykP_+=Op(})Pp};pKfQCzkOx;Oz%{b z$sFDBp=euY0q|s~Xdx@L<@`v-?X7c*OygRrjr=fe-rcyi`moAz8_QbloF?O+7v+jv zfP$*Gk@Tl=@+Hi?+Gf89hlct|ai5%QQc16bHQe-f1dD~GEE@bj=agj%cT{QlOUj`( zp$vut*nA4KAEG=atYSP}y}0NDb4W~vXGk2>G8&!nR*U_}5&gTl%Y$9lk4j3K2q6pZ z+WPHF)PqeC>#KE=`QI)-zmK^hqFd+ZC`kW~tY^Y)^vTa@Jqn_`JS5Ep-TA%U#U7s< z&U1bD!h58u+9_yfIPgL{!&BmN-k5>BcCXBNFj-104cx|FS?R%sAA3a2xrru^=#tjk z&0$IJE?o3bG@_2zA9l&u{Mhog2uV&aF9##df6^TUcVG*!JR7 zffvb2t*DkA&#ez4z*%P7Cr3HH=k+Ss^zflfg!`XRgUdJ6w7B)A2zOc7r|&Qx?y=M} zSUO^5VfK+}-t5IuKl7)+mXGKmZ{+q^)yN;xhk-Y}gT%4V_#!mr5ouaRBKTErskm&+ z2gd2FH`Uq4gCi%$D<-D>1D(VqYTaO3;8(N|eFaAW@nkZ~k3NP&hx*4W21=TKA88rq z#Db}r>ux+6ftH#~*+T!8fwhLeR_PAXcjXcr%PfucHGOL2qe&5cz$oKRH#j+-^{ibb zH$9Y5!Rw)Advhss*1M0GSIZ_FxL?%_$88H>Su%cJr3DOSw-0?PnKuHSj7(`9o{MF| zGS+w~K4)ONqWS|S<@=oLp~^#}+unuD_&0!#w7#vwBd)mjxk!tkx~@p!@)g(q)IP6q z75|k}<9xK6mu+nly3#-UueF$OQS=f9j*TY{c-SL~%_1^xJX>D7Yb%3F@+n+cifkA& z;6b*{HLj35$Cjo{OxA~SJ6@!*J<#DE(#pJoq0|Mxh29@nSHzO9A3ejZLgltwKk|Y# z6gnB%l<_V8v^~k%8A&rb4`8z(_=3?MEaHvr11Hd!?B4YjKXEmnlhiU}GAH-WEQmh| zX1^`N-f{b|Vo&R=LlPlwLNYxeVZya9YeDAu-Y!@5_A6{#pVO6U5ey;ciDW7(`<0{6 zt1ES$LytKq_2l3;hi}6Gvt7v|{p3C4x9+9<3xp2tTsEm1G<_%^$Qm2iX1O(IWm%o( z0VsLgU;l>b>jj8Ig?R60<2hY%anBZkm(C)gE=Wa%tQ;!u^D|av<)=P4KezLrc>Ma3 zmhFS3H1u{mF$BE@lF)_Qz+L4}#l=l=Hs~Wk8*}g!(dBsux42_$-)KGUo2ckXp@<0G zM)_?`7rx8>KjiB>Rl33W^Pn>QH$0d$a`{q(tYDNfInJZ*RYBq{u`}BC0VWHIUu(D! z$+HDrXRk5j#V^nca2%45z&TRBFuq+|;p+*y!vJ?4OFvaHHLjSEr0d@mSZD<_Cq zJLCGPX>vqd)t{QwpzO|eSK&RfD}kDk@y>s2tf0(MCKMO%kLO&Td!VNY_0ArdnUioN z3-2(BbD4Ur3|YL+?W%b|>irlbV&gYsdh}+v=>*=;a-O6`k#SLJbsHquK6E@dBAw z=asfL^w*kxk!}wTeq%sYqz4k;%sSLiPCevlOBj{T zKKkmTXKV~r)TQ2faPfCyDVYG)M3Ouf;JV-|lR3%ou)lF{@=cg;w(-XdXkKwIY`wl!%|BGAK8YSDgDEm?|<1o7K%YVu!-7toJM%8~&Bl@O9M4C^s zXYbh2~fIFLQ5u(Ax&F$LEQgW;M z;+(a$RcLXsyU2p9UkqB9jaeI_KT;SK@!J*D;jwTj+YU7L8aNO{smj*UEM8K{+dFah z^lL^SyQXFPZ`3-HKC5dzz{9AM913BL@?f5=#3xh2MU52?%y{5BB#`yR!p&Y06Ey_epZ^j?r&UaE^(fVwXKtAEx zHlXmw^#t|uBlsLsme-IU;%DQi%s(@iKb~tBoE+Iyx%i8n1~DLJ?I%#Tt%ik7 z_!UTOhvk96O6-yG-RNlYK;zDNfsK!MjSI<=UyuJ(BD~Y`>H4!*w*5MG*FhZEhN>B1 zFDrsr9+T260oy5U1r-nRSc$Y}r^njn^6Hf_6p9ej1f7o+Avr0y=`nCj<9$qgnK5DH z7~-&;4R1-j$O`|w%y%o+J!$(Baaq~u)gw0=;cCw{VivK6G2m3qX-$DS$1_9~ij@N* zagoyhq~xh|!%z2Zk1s*yjfD4fs)JGW1VM~hd6I{G-^*k9hbeb1!N*|@ali8?WB__Y z`ulOeBmRO3Yn&malJ_efe5qg#o68Kfz28tM89eyH%@!GI&!#_vq#L~KNWbk2mP+%@ zP#G^T!b%Uh~x(HSa2@&^1DWB*57F0qEle)Or7ZBLD=e3G@{6faij>=zL zHJmDqSK|^(MQ5x;ar2e+R4nzz4-V1mg~>FNP2C)Nz-y{|UPvt4=O z9u60dMD=dEvVSYvnof)l-2JNXwnI~44a)(%-j;l&c`3mtvxd#FmGi?}q+sg6L%_N; zbqKh#it7%X?FCY$ZM~jqw+of6WECOu?1RKZq+aMi+n7z|>=}20X6f|PnZxK+CMg{( z2V~Blh*Emtb9R9VF7aZbcOFieC*XD9`(G8}MXJkWzq2Ck;2j#@%a+7`N$d{=$Mo$c z{%}a&5)1l@)o;Yc%@24a1Y|JS#+nw*<bMB8OR9b z^c?c!oew;tvlgPD+9LLz&&%oS|8K#{qq@ALt`z6 zWc{8pzrNm1R*DwHLqItX_Y*Z_me+fy#FR(R63zK&z12bxT5WR|&LHRO(VwW2fLL*r zl-OwsGR(~ha&$6UjcdL4l4s6`^iGued78exZ;*9}dTro&Nmr;{gB-Nx9 z5s3J}aL>x3jX5*$46yj?;Wp(87Ubwpq(E=#!2!@Id@oqT4)AFHL|_+q z`qCgpmn`{ZhG-1khEz=nr~+p9v0r>rS;kK6Uh$kbkU{ zJlHZYJahgYlv=!gioUsMq9Trwh5g`@Vtc>~p-_|Sy64xto${Nv(RTo+k9@fH6_F&L z!}ilW+!t#a_x`r`5Zs&Z3!AqmhGJ9t>zK*DK|yAA@?{BN-~*gxhO2={?7->0y*xfu zclxP;gz0Xs%?^e)Y6)7iYh%GGLEE9d7jA`3OujTz&&9nifqYy0g0%Se=R&ij#F$3q zi26FXC&#IflJa8gyEtRtQVKGh)UI;uDAd=VosUp0&c!5kZ&kVCxBqV8 z)nWNL0T4J+q+$W>LCHzz;=HemK*_3c{5ID8%dNweHP&I4cdoI>xppv#)Zg{F{fVaAP+v$v5xL?}9O)sM2? zea-D68@cQc6!?LIn2ZLQ@71$UXPrSaZy7wbG>38ZQbdnjt=L@ zthGjoLDF@AJ-8GE6k?>j0&WoN-gQJhS^W)}Wi8%CM&|w}lO(g5o9U{UUL-klYyPHJ z$?^(CE}oJsOAbi-$fNhuJ}!gqnz!*YzNF95h#y`yY|2D@vpD>#F)a>zebLJkH>(Qx zj^U7%*djjsCP}m-Z&$H^>D5PD+tqvi)_#sh%*>K)Jy7DS=g;FeyEQ%)kA-IRLKk!$ z-E4U|Lf`tRWLeTDrdn~Et?O~PjkIxDPw6q|2PN@=;MgBaC=q)J5y%&xe+ccHvyA)T zq9hF27-p$^3EOZjA0u6m_2vw^kP+cW*8s6eK)LuaMrw8oox6VL4|4Hgo9!ZSkvabq zNcs8<^YSBXer{h~a&GoXZ%Ajeit*w9M%jtF;7w)p^dbB&-0M)TSe+k3-=u~*jNQYw zH~IV|F3s_|o2l|tJ2fWD#?P5>Os6yM)cVOQ%!Lrg2{ZeYY06HZB}SG;Smb$&WmH%z z26Ciz6rNANT;!t95#T&3T4c6A_DbbvZ(RyI>}Wb_{or(rO*h8!bgqQYlpmOZrgD*F zz@S(2@OizTW*G?^ap-hGAxKhXGCnwGQ|*Q&7@ zw{)8KN2o5FIlJvKyb9#Sc1s~FEm(KP!;k#Rh*^OQ98q9MK2=!4qYhj5?(U5NvdXM4 z!6W_)(%~hgVg#^7RAH88Uby3#_0stU!YCH2Btp2HS}#jfd9&{5aw2nvHW%Yu$aBj) z^xD`0M!K1B`a{)Il~#XxOy(BcBOeS$=__WlwEVQ)bj(we>{ zk?r8DcZ3SHo=d5Eqv+`V@P4`lY+Zn6pmv`uc`Y%EO^$}%ylz^`!-f6>?& z{}A!RYmKY}CCG^O;`8^A#9Ij+5t5B;Sl{W3fwMjE3@xoUiDMg6e2u||EOldg2gg3O zyr@!J25*eo{6eO4?nPA1Bh`{28wttCBJ&+--ZWnBUX^n?s+DMP@0eCVrTrMZ9e#Rq zb%_DN7ZjN8H_vCn$?6&=c(MA}LHT^_&2H^X#@NpBL zHt#AAv>bdVHa@1+oW(oX^u!1tQ(Em$f80vTxAZ;~gepUDK@H|z+#~Fl5T!*xc6|> zypi!6eqOyELdT}CvF#0isTIb>$2{TxzV{>CGf~fxhK>HZ^?mHV`Xs=4ElH0~$(tb_ ze(^}uijG?KIQzvIVr5{XEz4OZWo2dj`PrMT+y0xRGpCdF9t3zuXSDwKLni#4`fv(y zg_BCWwkGOv5>ZS1mLXj2wosPNk@a^Z0vYvR%wxLfO|^8*01l%J)$D0XVnG-D zj+o0)()`zK?M~E+2>HH5M5}7n@ew}!j7NPIH(F8>Kk}^ZzDvsh=jcJ4&fZeJR>37P zwMspnu8BJQf+8)ngPP)ci9`v61p9%(x!{vRZ}&cM$i%oQ%VY;TD?PHVPfBmj2&mrT z8FAelJo9##+*!Qyk;v1_uD~$M*xAC^e?1apDUJ~pxaNpm*JcmIU8;a|gZaPrrqBhDNVy5rq&%{ta zg`j8c@||4~&P$o7&rZ2adA5y0%S@`YO+1ETq8OL*gmr{xQb0?{}44uQ~ zDMBK%{w|gwX)ID~-{=rY!H$oqT1y|nUI4Kyp&6SjPTBf`t{W9U5;nJ7<-(F?zLQjjikX zG72s(c`kt3<uY1KD_Oy$LAVC)B%#is$473pL!5=)#8lIhoR2BM^fc+ zgwAvIBLZr|iooUAgjO zrM6CK52`@y>qzx9)}_`nQAP$BwRW{}MI~VnK%uS}y;_uZAXFZ=<)b=^LdgN}laxiNm#`QD=S3`@R96#sswm0kbs?So6 zkFA~E36)Z5Z~~#^8qW;QLp%B;FosLIu?wQ{_N? z&?#-J%RD&r;t=i3RHLzAb9FoB?V0>f!t(Q{W2F5|J>7-d{m zvDYaZalCYBr3TTH*5nW+e-eO}dl@E3N3WVzNl9(JFCnzLK#;E>oZK`JR2G$-(7T~v zjH%@4ryvM5a}??18zff-RGdAp7Xe*8N32&|e#&L%)8M~&cC+^suwS_ANMFJI!)3@$ zYPcKiIoa0TPodbnYUp5Q__c~OqsR1B$dQvF+jjA!benxf=$gz{%?P&P6GTKIPsLBS z+4kxl5yTd{FQR8Z8$=i^{|o2i=wT6vhHv{xX@um=Cl$~Vklf_GZkGZ@KkKYgp?-+( zG3C#sN`wBI=v`9b*C2WHxH|6Lfsqv*NMA@X)~~~}JRuD70-#^o4&OcA-0@v$y<4G% z_y55`V76xi;fYv}Z3@{b_g3&?o;$#P;xn#cY2kuWgCIkLduQ9}#&^TF{8+<}opnOa zF~+u9DV$H__u-06druhb2$NoS?vEa%YL|_=lDgSvV$O^!BY{5@_~Pw-@y|#r`Pwb+ z-ZgvErJ%fF`>lGWH*%fRCy(|&r(wo@4%jx7j%!aJpHpa`7U`c}m&qNbuHxVEQCZfw zX~Pe$9i8R0?p$M%d-P8)CW^~f*uE)OA~YIGs9rP`wwTs@uHT1CUO(-ob@+Ote|KR} zP2NrK>KQA0B}7L6;CU>pT!TBof0j zboMzUziW0l4wxr;CK;`B`vV$_U}VIxie(pU{=qur^Ws}&F3W|8FwV- z-QwdQZfL}&cMDIIMS{3J1PmtoANYz&Ca!rF_zAAj=H$`0QJRH9zI_HpQkuSfD#=ck zgE&+w>bk(rWe)H6l>I1<+Pzlw-V3a`HZyFg*Ko>x;Y6R*nMMAN|j=QBG1Qn zHmIPVK-Yb0>ThrMldw>PG3Y~G!%yZxEoi zp`8$mmggL#YuS@_l19)s;=NjX;NauMTbi5RaHRp z*_Zf&3s+Ytqvy2~t)6eOyxwMw)3R)h?X1xl-||5C5M1SCYh31W!9utVZ2#)++&PI6 zwV98p#}T{|{eQ@QF1&ZJSbwL&yLbLuDFYpqQsS7)NS+FKojjbT^OXJquJy6norgHo z93`t111kGS8^U2)#&5gDxKT|;yBQu%YwbOt5Ul@gffZzoQhn=iAlF{e6!(DB1{gC|!3u!z4Aus1*2Bw?&%!wJuYo1P6tLQ@+YF|#`#Eca_V%JLm z3gCz|+xv<@*yxwaZr6z0Izm7m|4)HGMWw}29fxjfKyL|!e*p@N-VB}Rk4&h6S;wo{ zXM>sMDB3xfgjWH!ysUYI`>j6*(CNW0!owOy?-pXP96Kk4B!+VQ^zx86A9G6 z_XIhW0-z};{=L*-lrF<2;%y~eLGy7?Rr7`u2aZY@`HUs|7e`>mkR3SdlH4+4hB#xg z+>^8$+OR70$b*;eIP#vgr`<`kr?B{>x3QmZm8LU}`V@~{H!}H>P-v4q>9n)vL=C8C zrykhrd zp9qeA>QdP084x#O!=L$WjmWe%Rd9B1Z?R$RZPhP0?wmC}E;iMyT^8DH;8-Lp&;s-* zdgHH0T*KDqMotxC7ye{b@S%OBOsOqZ;A2F-1tyL5X+hQwzd!c&MQ+mG8!~6OPv1ME zOhV7hoca|w8vT8_!K@G_A7?$5WyVRZ^fI>nRGQRcX9VLByJl(DOQQS$iMUE?Wv|mf zM-sHxzbQIl6QUq-{Um6sliPn=j4Y_PWAB5v>7W!&#g;}Z1+PmEGK0(21mqD3$9`)C z%OdWh;egeI^gW0@7XV}SUXld7A@5UKwf*RRz4&=C7N=iyd6$H>6;col+y@HQKuq?> zO4_rn-500;|C{i@)7!+p`EcRGI_BO<7AxYg{zGvX%jrB9>0L@?I#TbMq(G9lt9;_u z=!SA>mx6{*FO{@`m^PYTf--+QgNtN!uCJhV1M>FM=h*n`Cl$_CfPyPmc8OdyVLqn< z^V|4*CCgR8Kof9QSIt)rRbke}ry%d=68zD!1fL7I$nN;UV5H(ig{JTdQu>6lY?7yI zA8ra(yCSVPEr8)!14qaXGvPZ9vvt^7$t)o*fc_nv0EiDoV_H8){K~%t53Jz_`=Nko zg_YQE2NMJLu8FU9tY(wKL-%%wNS#-$W2VjsT*hgf{)eNlPBoG+)zWqzV~7jgj989gf&~tU+Z3c z@!KlIf7MnDITcQL|`y0#~d!u5GO4(%+_b`O1#oxl%`@qs~UUNHA;Vb zLI_C`eW9+SMYY})X2k*5cC5gS=|kVdE9m^ohZuSC7X@tW2|9mpdq7|r?vX6Xs%{te zL&Z4M^*+|o{Yo8#O`i)?a1Jw5(rdKEZeU1KKfJ^)($&*g$T)ydaPLl3$^sXOIZBuU zRF|KFoX(zH?XZoIeH8n;!zw^=+iV4e(Ddgk-m5zp)GHlBb{7h6VmJt0j=0GOXio4$ zvEjOA>P$6A%Dr~^Ymn3v%Upld_85^?bJ^$iq=Z1bJ=@{%q@jH<`?(Rb-_~7uv7I+t zPlb!cHX$8p1hBDDD*uX>qntrV%6ULP#X^&Gl;5a4737x%RjuQ@>GLT?CRJ;N88SF@k@;VR?b&-Hn8t@RVAy{bjudG*Js zg64l%xqr7KeZPx_XEF{@-VrTePH)(3#~a@P=qdWFxsUD6+q70p?;UrvL*Ob?ZPL8# zilW!m5^aOgwXVZ-;>IRMCOW&fzsmP}fAD=^$|VevauGyQ&T?unka;9!q#EqXuU**1 z(m|$*Vh)m3)M*VzDU+f+bJh%=pWS(t)cDj#WGSDz8T#xu&e?qF_gRV}X{jxGSMP-y z8#j*u?aI3}4>b}Qjb6&F;~qTMb2x8*nb9&ck#GwyXPzL^$ileInQFpCiq>vMZNPiO zsWR^VY83xGPq;<1{&W z_7X-}TbzItX}w9l18))(NY(--wy;?wYU|@L%wyg;lx?!_m=2V_D1HQ&C%+7f)B6$cPc<1K)OkzRQpF?AF1g8GVVoxApmth_sP~ zo&bAZILGPJGio9ngHD=}_1ZwpR{iSl`m9iR#=4*|a|`+lBCapjFQd$M{4gcD?&m&q z{fD$MikT|Q`mr2@!hfc}YX{c{PFMHBy}(CJfN68+2LiqOw zZc3gkSCn>s#qZST-n-aRhgb(8PYT(IYN2V@4br9CwZh1hE6~arOdcsZ7l?7-ZcXP$ z`_?DgE{vUF36|D>hH~1Fo+&vxdPc;u-EHCxgQ$)P>=U*Ss+*=4Hbr;54eY`CDMM7?r)?xB)(H;WGKlRprl!wLLNoXRgkHdtKOrgc6xla+RE1`W+e0+rQ%S!%)X}FO30~&?T_h7 zPEVN=TusG;684b~{;(Cs8g-%)_x2knV?T)k7f;0gN;Mi2Ur3o9IW`?BfiA@g;uQOa z1{{P3lu;Ok;v3NK-@YG9OG9P@?p20SfyyOM65^?xoo5d|8Wpl@T0X4JFeH#dobV*- zgsYo%dTE45`$#EKLNCzz#QVO^PG)VKe9s!>=oLxtHLJ4Y^Jxy{o3ks)Z7aKxI%-JU zZIYZLD?WnOH>>Ek$m5A|>Y6*Axtwpw(D)6&EIJot%~h}kKDyuEmP^Bl9Z^%_S(qSEKG(%JPY z0x35gQ|?1u6p^C=r-w9BRe?sDz!c9#stcw#A)2QJ%}h8BiI;%w;IvIwC&nYxvgn&f z)hz-;b?~3P#5JcKdx82a>d*aHPm4VsYn7Y2CWk#^&<_L3SVDjSu>RVvq2{NDV2pYN z6U^7wFda0~00kK!Fb+Q;ow8TUUSub1Aa3rI%q?GdJsBtcLky(BrshsB!@lH`CAGxK zeUTuk;iiVD3oENEzyA9x?g*EJk2Acmx8ULi2}p|DJZgl(MV?#uUl3&m)YNb!2R@G- z&2p0B@g<*5S^k=5HCZ5bs_G}O&H4xybrVs-Dw2JYX+Pt< z_r%d1Zy~@IIOUsDT3oW7sSanEbK>824tpw>@iktKH0PdEZ6%6!?X^E(;`WUeWkg=T zUQ}B>s$=#wuj;o8g@3OS_%b)BH(b)rL;_iCdCL`O$&26rnjce+bfHpy%>*#4*DwP4 zC;17Gh)^`yo4fSl_neCEQ|Q3C5&v)&$X9Vsa=9$Sj5YWPKX=Z73=X@=G>Fw(UNZZR!lk{P_0V3VD2_xc-UVP>@T*5;5-%i8bX zzh8b0(C+xCCvbfp^BW&d_oms^{#(1YG8N!D`L5`(01(JWIJ0up<7#F@Q2YV4ljhUw zOFrq1R|ngJ?b7QFrD&aFzOhA&`LWJd^&#L|VKML>Y&_*L9TK8eetwiOUGxwXc z(wD=~a!e|L*9dQeIvv-MaqCXMbAPH%?4)Ox;G+d`;|>mtYIC|MK)+Ld?QDRB+rbc! z!(tNv$K~VLL~2Vgw0^GcK*Q3z4Y|!;F~9yc;*VGdTdqJv@lO2?q{$V=dd07DyLIXP z8mzaLMVXdM=nOMz{IN)Zh^8brW z_3MsqW%|r>h<83C9BHWkvV4!iTtnX>tVo*H@!SE10A&Gyng8a5g%&rG^v2I~`lZsj zVMw>`YKr<&XsV4aOo~)l9&+x>@eG%Y%To2oR)+Of3FbV^9BhCQ>(oNf5Q(*4>fF!r zpsd_+5*~y?A?OR?`rBTAU{SHBlzOmdCgvYMrkjkIds!Q&K{(~F()#z>{S);d&rm)u zuSO8_6%-{LP%Ys$y|MBSHX@01-{94^VUOd*Dq8A#X6&9XZ?+1yti25Os!ds`er3CK zWB>PjwU*sOuk3H=TDhKA>|b8c1Eki=QM*Ob23$bd8k6Yht8;-miWI6y1o)**%Sj(U zkv*R~e>#W0cFjSd<22GkSedi;V98>ncCNA%$LMA5Do-b9u!!!ZBiA4So0;CVC>539 zzl#oAD<&mf=#wguaHg15%i?Gyf;NGXMn4A*>pZ3x#I*(nYQ^9!`jF9hy(uL7vHnq{ z{qC``1!nV3IVm9uPIlFVV{zVT5rR}w!YHBGHm1&Wa`Q7FpGP-XDt<#cS&~wu1}`F> zHYAli$7NXv04Ls0D(YCzKqWEQ_)++;`7H?n*%0OY7-*7&t?iM+M#J<#%i{(EAanQ9 z@iqTfooeBPRKte>p-fOFPf8+!kL|nnibLX{f>DdjU#ym*uSE_9Nx_3f75?RrXLaL^ zRv;Uw*75!-TQWo3V?O;wbuOIO}pxv|%T zE&7Bi6*T)WQx`(f zI0cHy3&fsKoFIS-o+y#&VI zvim==SC?h;gyjCv>F+UrzyF4bzoTtWSweG>XEy{jv<#bgFqcm@*^fi=cj|gZ?QRRE zu;#n?80RRuZ`8!aA$?|llpNcTUMhL_YprbDy-L~QjN_v6`q}Fo;dl1yM{l1{`5Q4s z-4{sZK!cndm?)Bt9DjHxzDyvqhYf!p8Ci(ACFMH3#Fn6_=u0BCAsC5vhQj4Y{~iS1 zR~#?c6(hjqq#sa~<*iSsd+>+^fkgCCVe4xkFbwqyvV8k!(rWseqmC|$&P0rTx+_9+ z4ZIT6d~Q_j(c`Zjd6&y9cDsRSUih{gu)<;MvqcwQcSLEo53Ux_s#5y*vV$#k!wZ)i zx9?8f7PSq&XVH0-p^A36#9{r>(h#x{cDD!o!c;*r{)V9UFVGlplZNUqt1!UHej~Rb z=Jx9Rh)IB%*%S(~f}B#52lu@9vog%;Sx><@x`&@^LtXc%+muv19TR_pUb4xJ2b?v* zoa*QG&dHJ;dw2hL9fKH2W;<*P!>qv$)UWRND~c@=2C(yMbO;JnFJc6dO+^7A9>yfBz0ax@SA7Ls0*e5`^)^uzEg6A8a$0h!vbus`W{C{*jfO2o@rn~5s&M`z3)%C_CiCb)E8%nh<CrBxeuN4`&2^LLYK#n3hL(ovsI| zSpLz|O0Uu1<%8z1_ihfUqk|s*0qP&1kB<9}k6c2a%72l7K%XYX25$r+U*wI@ICwV^ zOa@Zo>>o^rmg&A%|Dlu$av#F>BzyN=J150~xZ%sF)ZO~UpenG$BFKUe&X>NGQbx*u z;@=j|SKe#kfQMOOf^_r|@%lUspHRd(Nz9kw)$~zt_6ZW)ayUvlrk`U!EuQ)e?G?H> zle9p9nMjvFZ_aa|xDVsA8S8IEF8dV7m_~0xl_d$86bNRj1$GEcZ`2vj$VPOTzIww8 zx!^3mXu2~j^PPVM3nFS^x@;7?{ZfV(zjCC zC*C#$vf5MQy)aeylrP83;%8naquz>sLi8$5+z}@X-&Dj1NOK~ z+t{EZnc}=h>_I7d8D`H{Y#;3PEa3!{&!be6x%sI>G-w6(uN%#q7Wvxga`A+t@)Q6p z)=rAR1WRTBvw_OYlo{q6`bGddoqO6Z#)3?RS~*fg#3Nd?VlpG)@{V09P@w^M+0H+| zEl~gaT_n1{+=JYJUp>>=OMc*9NwOq368P5((B&zFb&R*&)1X5q&k2rx6Dw4M zl;>*!w@H<;u2h{-{6K56kk;#d>_2U`V&Gq454=PZ+tg}v`skmf03QTSJ8TaXQgS+P zKrm*$&=utBL{ZM*=90HC6N*1h(gGB)(>2`H-9$~uKF=8`lk?DX#lz67>j0^xI~Q7` zGK&j%EGp<-V{dgkVZ`dFYVD}xEu%90D=||D=FxOaw&-$iH}JA@UTJW(k;Fh}m`Y#n zuCEx^*}ge9%%ki_qEqronb#*Tt<@yHibVC0ml%lL-%3|ZjvnQ-Z0=35gU%B_JizoXtDvg$)x*>y znKf}Uovf{&Vf6Qt4t>Dz^tp)pr@`qdW^}|+96NM9clc`ZD+XZVgDRR!Q;dy?@uj{g zjZgB3@|}tG3IDdkDkf8iWXYBA;Z`mY9DVFz9c*^gcaHL|(l#YCE+d=3ggazguayJ5 zdE89hj#W6SpKVFYOLrA&nYuS;;wF90l}7YSa`fB~1&ZW=_p2}+A>+D4v>D)@Ft>YGWn4wJZJn^~9ri;bKLx{eha(}JCOpUD|^$&#wHfvwh-@{IH|b|y7G=_g+c?&q_|-Zq?EV06iTNi~{LxJDr`MnSzC`O)#UoLQ5Qf zs}@Blx0zsiIf!g*L?zc(6MjFYfS8!GO8b-jU17}4z&zMx`+$?~I6jmmnFy)ftYCB; zTfsU|?`*Gq+w5N$l<2haNg7eHJQ{C9eagss>&%rw@k0wJbbunL-E39U?(0S{J3@~P zH3l3rye<8Ev;LN7TAHqYz+G4wO8yI+#lIisT!xivi`ib^M_MvApLNvzu|sjpfosUbJ+5fN zDUG-7(Qffw(+y+|w!jhZqa#Q1Ym))f;B(S=5d{1L_DU!#AMK++v|jg}V5AX^y^BXf zDfGfcXhIz3Avle{Afks2TQ0!N2az52T{+-LLE@Tab!}lTB*|_<_km)1jj-9hxBFLi ztwZObg9DHYiZ?v*cP|5hP4i7_ym4ZGXo=)ei|D7fw|{}8^cOiP$Z484dPVkIsmE5P z!%56Tgzv8Ae-VB&nT(*+3OC2Xd;DiQScUPpl|mAq`@X+V;P}qbk2S;=8*akf=}WMv zoRq7pEBlfgr?;XH-m!8|Au4)+?5v_tD^%lwSQ0O3{dc2k`FR`CHX>p}r216FkHX6y zN1Q+8)H~(y=4MYo@2q~PUAZm*JPav8@n&A?U;=)Z^a79vey*pLgAfqEmyvw|$_6}R zYnVE!U99Lpu7&cmSYiMUjkij)kS&!p?e)IAg!;?1z6u}--@&-H67;$N-aK= z5Z>K&{Ip=2vwKefeS*auz!oJNw;z{XLpZ_vVN9zv9lZEE8R*N(Y46`_Cxd{rL^$z)WPHvyCa#WMo*sFh`*=XA8lL@s-u zwvZz@x%FBvnVY}de9uSxJ!IjY5*uo@`7}6*cAjjVrS-G9T27!%+jr-jx4qA8=Isk$ zHu_zDYd~Ld0X_3q%{8WkE#v-H0kkvTrG={mmKJS9T7Z&#P#x9rpIK;8Xpw-#q8?|*MH{$PAd%d zrh~1`GOE+4AQ6#~(oDS0dgr`DVJh%zARGq!DeYddX1{eq>`G0#Nkvl%V}XaARtC^4 zLaJ=E)oa45+)R18Yr~Kh%VwbJnHa}YOYM)K=vuyTj5krvB3#Hx<~6>0@-hBu?CrIH zQsR8^xB5Q+zv^q1s9Pc?hy~F{na@ccG?54Vf6}3d$*0BNPRw0g4A^FEnSBHEaGC0m z-b@=yd|C`3N00W^SY=J32fkAva!8`Cxt&@Nfjr0Ba!g#62!*N>@xg;w3X>cBI0)YI z9-S|@L})&TEts-E^iN0KaX9xT&4*~gU$SuL)5i*~1~zqN_AI4RBj%|W&N-_rTUDym zZr%uePIaZWt&ZKx)RXet|JLA5Z}Pe8R?)M_RD3DfcmAZu#dWa_n@yVU*Pq?^WXLJy z^r+W*9R|vu9-PMxp4`QfEYP}&uMz}}hV%E&!dHP_4SScD4YvsuyA450vHpmozo!G! zpxpuE0bWFjXmXaxn!3WnjSND!6S>&=2x|CBeJN$*isXUa!5C@Jm$v8sMcnyR>efak zspK1)Wk2u&b)7FFtTnO=H^HjwIZyjvRrg;q8%Upbg_b2lho4ymB}2|z+GRhNCSW2o zd#a`U?7+Jna73VYrnueMTI%jM7PlmsSMTsyeCTc5AVgegA%qONbpit(t;I$kqRuLb zqY3#6^KotHD*5**vAru3Z9iK2cN`!6EwLXzq2#CWr7>rEdzdBB>QSCs@jg2!PY@2< zo1lGmTDhOnAUDG&NyZx4kHc3;4r18!x2Qg08yCc> z{Ox6k&DgaU)v2EHL~%kJlkK>)T^;S#P6s?xbot2&@&QgP9%;9T*WZi0b0k|$2dD-? z-dhHvq|bkE!cfyX@Ekut3%KAG1q+98?**-v^`e6>3w6RoLwB57N@8xsnXJn3EXR(- zt)}T;r5bUC@#w>C#{+Ll9qA% zzM<6J&WWf8X#ZYX;)8yjXTSMs!UvQ*f@>F`55q~vt-lQ3KlP9^ShElrif7Sa0Upk6 ze#G(H>MBdSB#nomU`TT3EAM$XPGz@__kyZ_{77a*n{*t6INlq3X{IDluLV3WFO#89 zSy7Az(e+Vy<0!1 zMnBz7qZ*t&Z~?na)eK%xeEWiD_YmsAc~1ASG*1VyWCVw_jxE#O^RpF}~?`x;q0T%5<_W)rqB_3KOOXy?;EYnzQL zPAq5Bhga^U!2_FA!Qw{J5L4b)|0wstpC9*uFz#RSMmq z$mfKQ?yObOX4h&nA2Z;cwq$uqNvgeeQ@}biQVb2_Ut;QAi)p|V&;gd*A#p4l6P;P~ z%ias#aIjr<1>4m|znN?xQxsuyW5N}EB*-26tzAs+L_0vJ*z3BL0pUZS8X@Hg6`K2X zy?USc#hKyoLolmRkcWT+`mEW^d~SG4u7f-QPJ=XQ zBu_+aA)dagUn?%{5`{lVGt5F}(xK%xM(&AE7?^?ja1=jgT#pMFKkRvMQihdiof%hZGm` zK8jB${3)t?OC3{{|AkMMs+kf&;Q_uLPS(7>P0#w5QToiGu~t6fF3`b;pZArn=8(^1 zEl;07!&XfbdPnicd)FTLmC}`gS4be%V{KzL={bm&U#imc9RV$wsNPSgFNa8u7L|yH z94+BVDu0Q(Q&WO9sONj&M3N35E;CzXC^}p}M$Xl`uDDt-Az7v<>m{ohh@;B2ae-+_ zl4Lp!qKla@53# zY?5rtmkDr==$wf}>}W)51U+^ZPVFd5Wke$+ZV`8LDteECEo^6lSO8UZLLUSk0y>g` zHZ($nbqpk4$tDGV0Vgm&ExF@k0FrjVw38SbLtnC>H(ljVNc03}5>ecnGb^(M2xSf> zd@)4|B%hf8NA?M$#5WeL+6$rZ6B>fa*#s#s3`|FKG$S+EfD7$q6B8n14>;ojOJs|X zbeRr*mw|(2(idoK%u9Mm!fAvc08=3b_Okbrl@j$VuSMpI1p5Eccbatvf{3V)DM60r zDhSUNm#?><#Qh??w^Rpea~}s*j?W|jce~W=C6x;$$eQ>sX1=x&GO#jX$PV|-?t5k= z*=PMxNI45AjHSFXQA9*{J}`1i?~&_qptd6~o|5@Feb zv?ixGblOdCyjGv@7Y%6{=QLkquMq>UVRBUkPb$vuKYPilb@P&Z1224-ek}5U*$Ava zLz~_i$)h}Kc#jSxaQk88IF(>arCf%^Iki#xJIQtaY?Xad^ey0*}O9fOUD#<}5x6dMYsck(1Fvqp4G?+R67gfqRgUa6U8hgLKWSiz{MzjH-MD|CzsuNk!UpI=tKQ{X+q-0v zZ(u+Ryza|T_^BJMy>9pRa#jMgpT<+WXkP}0NAG}NXtUU)binP(1;gcz$=Iw8dzSe z;9{O)W9JSql+*3+Ba9Z4g>B?R(LZ)Q$o;(UDeuV4e<_e~BZnn({9|P1b@@{TbWL2o zjv~^ymLfxo+j{M&HkDk!K841&l z&yVR?Bx{_zt0AIAH9uUBgZzXS6ztlNK;#IL4i) z26+W`mBhz|FQ<3DBUFH4ufoHP@y{)}m_SwE5A0F&=W(E==`>z9Bh`w@WwQo)KMLkM zi~8IZ0Fnp(KJ}&KK`EYza!PPqcEyd`hpRCkYlr6_RbUaU5A#>=Af$LXGwmJ)+eZ_& z=##~-nvdfvVh9Yj>!)&%awj(Foypfm`Do0;qBi-+cI~fB=(g0St7oVxfoB&94T7+p z%YbqV3Iy~){2r}m)vZ$asPnScamsH6J@^SI7^ff)mcBn*UxU?$>0E|{UbHx~CPg3Q z1?(frJV@8G9N@0kQBM%;3InmZd*)H!g0gaOQ3U;*EF3yYD8PI{$Th8=ECIFWrF@ZyA zvh{CST1=nT^lf~MTBN_Uq3u*G*nS`Q&WBKoEHR^o@3@9{RWkH?fWr%zW6?)c1?7oP zN4&Ils@`w^S`W612jH>yabC4>@c{L?eeRpz^OT>2=n2$|#Mc~AP&EFN$Q0xZZ00+% z9#n6WHRsn-H5}m&2MXNTRMD768R|rHkwpMh5pysdXDruQcUDCJt1i5LccP_Y{(9RyL0<6T!_JU6f&5fde^DSzet0IOYDcPM$h2DD`vBe!#}$tz_w$ z*DNVf0Mpa0O0lKhRL&srFC!3K@ioJW_37M`&D%%DZ_sN%(&4^^eC6yQZ*|eYncW+u zk15$de+(pFb~J=YJ%D!pAOUx7q{OCTb^B04fh&nck5yD1s3`ow(VmuT1t5bveD+Lz-(%Pf)e>FuZ9D`C{2uO$2 zn&Rv72Wc%RofV`U0A(=zH>*dxUE%cLAhJc64Ig5?cN*qX%OlE>IS3dSdmS^95 zNUJ=4Wc|(H;+aWM1^~C+<0?x~LOr%4ne_cVLZX~MTLWIwItGC{K~t>@8S6no9IIx+ zh%??&h{n_tjo7;mo&|1J?`!i~ua?uhxFDH8uZbx;+QK?h;ExxCz4tb1{`6`WaPdW1 z8Y3DQOHwBB_jFA$_ah^^EX>-kFqhEY#*Js4Y0sx!D=1i;ekZ0XUa)M6DLxxN?sj|K zhs-|cA)&YJMw0R$c?Oo9@q1JG`9hHQq)$L=hgE1$0uZ|5hVpW{A?dUei=Q|U=(@CM zwR%MF2E8D@J3F4u2)KTkLkZhb0n+%NV7EnlTyfhCq`~W-3RePmmhF| zGh>%dR7&pI&72*ldk|~~c^p!3UH8wQ{yw^sNrKLYCvmtSzsf;~dXuh_=uEKb#aH0IwhzwT7=@qtC#zp(aJ)~VVE39$ja_#^MiT%B7fA~o05<=(6UdozWV;h zIb@;cZ`dPa6Me`9eL#oGU(6;rgS>5115PXrz?1oDtXj{llP*ABLC$=^#32u!1^0 zNe$||v}Nse!pGQt6#lEd>0!$Iw^}iO_BLZV#%z1ZKZ6ozvLowBD)H+lnm0`^x_zba zNyS+~^u< z=YW7-U5cR=cg*)UT#CeUND4sdyeo|w-?(LPonEB$=$G{Le&9TI18sklq_W+xA6M{! z{dDrFjs`cIbPG{mG^IKyMNTc9_*zzjw|PB*@c+R%0UPcT#X$a=$g2Efux<@ ztcB@;NXxLm-K6Rg-UNV-O#+_}u7Y@ONPs7J?;79qCe4S%=OH)#gAGP}HJ_khkH+Pz z%Cz?=Snz|Z_&j+|DfrruI#A79RDA1wj1^b>3%b?;;0PPg!9pyxyC?K}w_82VEfOH= zM`4fJfgwmpm%cszpj8L7zkQZk@k7h^j(-%fxlNwEO<0Mo-j&f=(*n{d!*U^TtDSts zRER_l+uf#nH)9|gSHIFvRZ_LqI+F%A&9-zV+>i!I#DAWZv)Z8JFCJ*{U-K83XT1-; ziypK-Cv+{~ySTfk2oB8!gIf}S=^jRDAVp7LUoPIGFyH5cOd*#L4eMggiPIh6hyBCD zFlACe$^9!CQX2Ki75!H@GwU}u6ZPnQH_Ft%ttTp&d?d)2iYpwQLg^ zO`ziDE0&d_0Nq`8UlG_*5rPig^b}+cUuF%1c+j%T&D!aTqf`P>q2m3A9X9{likFF* zLSX1luQ&CQM!6$tcHO?w15lrmSh3FIU`;RS1nYHWd5@F!$2k2?{ak+E8}IQ{001xh z)a?B6Me7qZDvPXBz`99kww0ALM>0cupWXLozU;On1GLpu5 zNzwU?fE$?x$cU#t@uxhU9}~pVwTW6?1f12;u_-Gq4C~QlW*+8rw+F6rF$2zAocOAp zGzU!XMyfF-fb#L`hlie3QNIA-~jm`!fAJIE|#r3#0)280&Di`gSMjjl)&GGU7OlUvP zJLm23yu;tJ>K2^;xP$lEAx!QN7)EBMJ4P#6N@(+1`N|ge><*KiGLqvQs3Cl|_&Rln zjIR)m?qU{$9jB2caHHpreKCFZJ=0_Ujc#?+4dtE& zNGQXP3&N`$pLjVz`!#Xdm*e=7P`tDgMK1~A%6wm6H1R4a=6VpuC3kt z2RW~lM%vlXFM(H&V@xBQ*wG<`8>^imT?4>10krHTW~g&Gsxbkj&H^M)6R|Bpds)_g z4O;XX`I~n_v}RSM+01|jX~7%N<8KX!oYng1w^wJxQ$xOW2(}t|dsD{P+5@%O37?Ob zj`3$TO7x|N*ln4|Fcle2utby}wNM*KH(zDYyC(fL@I0)}) zd+l4h#@g=>XaQ%{&(7X4DlZeo-wHYtA?_fo_jzOXS~dWV4V_AbJ7=p5z`+LOF8V9s z@#pKZ&}a+ZtjlRCoC5ZzEvK5ad(C<;(r5KG9rq{yD5U|~^jlewBVb9sCm_C@sD%X6 z1J{$*!pg}19||?os05+VM@M#}H_v`S(Db1vDc&eyT%5>fxBFI4NQZ}|qb>3*g@%M5 zqBvtS9cxce@>JD2W&-H9{HOWS&ZEoGZw+q<0B(4PlSckm=g$vZ5J6tNo^vjDIP6DT zoo_c4a32vO2MG%cb6*4OxdXr6fXfj_d3sJ$E!NM3U3NesivE0Sm{p=2uq6lBctSKx z*yt{0zt)(0*D_cV0AL1!^0CRD$?3A5E~BEYVR@d0&OcSz_r
  • Y^?!F$0UQ@<;$}N*&e`Q%mEe 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 + + + diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 70313e8dc..45fd12cf6 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -429,6 +429,7 @@ "bwi_settings_enable_notes_room_title" = "Zeige meine Notizen"; "bwi_settings_enable_notes_room_summary" = "Einen Raum für private Notizen anzeigen"; "bwi_notes_room_title" = "Meine Notizen"; +"home_context_menu_personal_notes" = "Notizen ausblenden"; // MARK: - Personal State diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index 6ba2911a2..d85825e88 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -342,6 +342,7 @@ "bwi_settings_enable_notes_room_title" = "My notes"; "bwi_settings_enable_notes_room_summary" = "how personal notes room."; "bwi_notes_room_title" = "My notes"; +"home_context_menu_personal_notes" = "Hide notes"; // MARK: - Personal State diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 3170fc2df..fffaa3fe3 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -129,23 +129,8 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou } - (NSArray> *)favoriteCellDataArray { - // bwi: for personal notes room - NSMutableArray> *filteredArray = [NSMutableArray arrayWithArray:self.recentsListService.favoritedRoomListData.rooms]; - - if( _recentsDataSourceMode == RecentsDataSourceModeFavourites) { - PersonalNotesDefaultService *service = [[PersonalNotesDefaultService alloc] initWithMxSession:self.mxSession]; - PersonalNotesSettings *settings = [[PersonalNotesSettings alloc] init]; - - if ([service personalNotesRoomId] && settings.personalNotesVisible) { - MXRoom* room = [self.mxSession roomWithRoomId:[service personalNotesRoomId]]; - if (room) { - MXRoomSummary *summary = [self.mxSession roomSummaryWithRoomId:[service personalNotesRoomId]]; - [filteredArray insertObject:summary atIndex:0]; - } - } - } - - return filteredArray; + // bwi #4802: for personal notes room + return [self filteredRoomCellDataArray:self.recentsListService.favoritedRoomListData.rooms]; } - (NSArray> *)peopleCellDataArray { @@ -153,17 +138,8 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou } - (NSArray> *)conversationCellDataArray { - // bwi: for personal notes room - NSMutableArray> *filteredArray = [NSMutableArray arrayWithCapacity:self.recentsListService.conversationRoomListData.rooms.count]; - - for ( id object in self.recentsListService.conversationRoomListData.rooms) { - MXRoom* room = [self.mxSession roomWithRoomId:object.roomId]; - if (!room.isPersonalNotesRoom) { - [filteredArray addObject:object]; - } - } - - return filteredArray; + // bwi #4802: for personal notes room + return [self filteredRoomCellDataArray:self.recentsListService.conversationRoomListData.rooms]; } - (NSArray> *)lowPriorityCellDataArray { @@ -179,26 +155,28 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou } - (NSArray> *)breadcrumbsRoomCellDataArray { - // bwi: for personal notes room - NSMutableArray> *filteredArray = [NSMutableArray arrayWithCapacity:self.recentsListService.breadcrumbsRoomListData.rooms.count]; - - for ( id object in self.recentsListService.breadcrumbsRoomListData.rooms) { - MXRoom* room = [self.mxSession roomWithRoomId:object.roomId]; - if (!room.isPersonalNotesRoom) { - [filteredArray addObject:object]; - } - } - - return filteredArray; + // bwi #4802: for personal notes room + return [self filteredRoomCellDataArray:self.recentsListService.breadcrumbsRoomListData.rooms]; } - (NSArray> *)allChatsRoomCellDataArray { - // bwi: for personal notes room - NSMutableArray> *filteredArray = [NSMutableArray arrayWithCapacity:self.recentsListService.allChatsRoomListData.rooms.count]; + // bwi #4802: for personal notes room + return [self filteredRoomCellDataArray:self.recentsListService.allChatsRoomListData.rooms]; +} + +- (NSArray> *)filteredRoomCellDataArray:(NSArray> *)arrayToFilter { + // bwi: #4802 for personal notes room + NSMutableArray> *filteredArray = [NSMutableArray arrayWithCapacity:arrayToFilter.count]; - for ( id object in self.recentsListService.allChatsRoomListData.rooms) { + PersonalNotesSettings *settings = [[PersonalNotesSettings alloc] init]; + + for ( id object in arrayToFilter) { MXRoom* room = [self.mxSession roomWithRoomId:object.roomId]; - if (!room.isPersonalNotesRoom) { + if (room.isPersonalNotesRoom ) { + if (settings.personalNotesVisible && BWIBuildSettings.shared.bwiPersonalNotesRoom) { + [filteredArray addObject:object]; + } + } else { [filteredArray addObject:object]; } } @@ -265,9 +243,6 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou } } - if (self.recentsListService.shouldShowBWIPersonalNotes) { - [types addObject:@(RecentsDataSourceSectionTypePersonalNotes)]; - } if (self.favoriteCellDataArray.count > 0 || _recentsDataSourceMode == RecentsDataSourceModeFavourites) { @@ -716,10 +691,6 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou { count = self.allChatsRoomCellDataArray.count ?: 1; } - else if (sectionType == RecentsDataSourceSectionTypePersonalNotes) - { - count = 1; - } // Adjust this count according to the potential dragged cell. if ([self isMovingCellSection:section]) @@ -744,7 +715,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou (sectionType == RecentsDataSourceSectionTypeInvites && self.recentsDataSourceMode == RecentsDataSourceModeAllChats) || (sectionType == RecentsDataSourceSectionTypeAllChats && !self.allChatsFilterOptions.optionsCount) || (sectionType == RecentsDataSourceSectionTypeAllChats && self.currentSpace != nil && self.currentSpace.childRoomIds.count == 0) || - sectionType == RecentsDataSourceSectionTypePersonalNotes || sectionType == RecentsDataSourceSectionTypeFeatureBanner) + sectionType == RecentsDataSourceSectionTypeFeatureBanner) { return 0.0; } @@ -1284,11 +1255,6 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou summary = self.allChatsRoomCellDataArray[cellDataIndex]; } } - else if (sectionType == RecentsDataSourceSectionTypePersonalNotes) { - PersonalNotesDefaultService* service = [[PersonalNotesDefaultService alloc] initWithMxSession:self.mxSession]; - - summary = [self.mxSession roomWithRoomId:[service personalNotesRoomId]].summary; - } if (summary) { diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSourceSectionType.swift b/Riot/Modules/Common/Recents/DataSources/RecentsDataSourceSectionType.swift index d0a3aa9f1..cc9d17b04 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSourceSectionType.swift +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSourceSectionType.swift @@ -28,7 +28,6 @@ import Foundation case serverNotice case suggestedRooms case breadcrumbs - case personalNotes case searchedRoom case allChats case unknown diff --git a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift index 85039cc73..b5a6522ae 100644 --- a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift +++ b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift @@ -148,6 +148,10 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { self.bwiCheckForMatomoPromt() BWIAnalytics.sharedTracker.readUserConfig() + + if BWIBuildSettings.shared.bwiPersonalNotesRoom { + self.bwiCheckForPersonalNotesRoom() + } } func bwiOnUnlockedByPin() { @@ -301,10 +305,6 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { private func addMatrixSessionToAllChatsController(_ matrixSession: MXSession) { MXLog.debug("[TabBarCoordinator] masterTabBarController.addMatrixSession") self.allChatsViewController.addMatrixSession(matrixSession) - - if BWIBuildSettings.shared.bwiPersonalNotesRoom { - self.bwiCheckForPersonalNotesRoom() - } } // TODO: Remove Matrix session handling from the view controller @@ -712,6 +712,7 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { } service.createPersonalNotesRoomIfNeeded() + service.setAsFavoriteIfNeeded() } } @@ -783,6 +784,10 @@ extension AllChatsCoordinator: AllChatsViewControllerDelegate { self.bwiCheckForMatomoPromt() BWIAnalytics.sharedTracker.resetMatomo() + + if BWIBuildSettings.shared.bwiPersonalNotesRoom { + self.bwiCheckForPersonalNotesRoom() + } } func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomWithParameters roomNavigationParameters: RoomNavigationParameters, completion: @escaping () -> Void) { diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index b0fcfb848..4250693d9 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -405,7 +405,6 @@ class AllChatsViewController: HomeViewController { RecentsDataSourceSectionType.serverNotice.rawValue, RecentsDataSourceSectionType.suggestedRooms.rawValue, RecentsDataSourceSectionType.breadcrumbs.rawValue, - RecentsDataSourceSectionType.personalNotes.rawValue, RecentsDataSourceSectionType.featureBanner.rawValue ] } diff --git a/Riot/Modules/Home/HomeViewController.m b/Riot/Modules/Home/HomeViewController.m index cf379f4fa..d178e9cc4 100644 --- a/Riot/Modules/Home/HomeViewController.m +++ b/Riot/Modules/Home/HomeViewController.m @@ -398,7 +398,6 @@ || (sectionType == RecentsDataSourceSectionTypePeople && !recentsDataSource.recentsListService.peopleRoomListData.counts.numberOfRooms) || (sectionType == RecentsDataSourceSectionTypeSecureBackupBanner) || (sectionType == RecentsDataSourceSectionTypeCrossSigningBanner) - || (sectionType == RecentsDataSourceSectionTypePersonalNotes) ) { return [recentsDataSource tableView:tableView cellForRowAtIndexPath:indexPath]; @@ -502,8 +501,7 @@ RecentsDataSourceSectionType sectionType = [recentsDataSource.sections sectionTypeForSectionIndex:indexPath.section]; if ((sectionType == RecentsDataSourceSectionTypeConversation && !recentsDataSource.recentsListService.conversationRoomListData.counts.numberOfRooms) - || (sectionType == RecentsDataSourceSectionTypePeople && !recentsDataSource.recentsListService.peopleRoomListData.counts.numberOfRooms) - || (sectionType == RecentsDataSourceSectionTypePersonalNotes)) + || (sectionType == RecentsDataSourceSectionTypePeople && !recentsDataSource.recentsListService.peopleRoomListData.counts.numberOfRooms)) { return [recentsDataSource cellHeightAtIndexPath:indexPath]; } @@ -582,7 +580,7 @@ { [self showCrossSigningSetup]; } - else if (sectionType == RecentsDataSourceSectionTypeAllChats || sectionType == RecentsDataSourceSectionTypePersonalNotes) + else if (sectionType == RecentsDataSourceSectionTypeAllChats) { [super tableView:tableView didSelectRowAtIndexPath:indexPath]; } diff --git a/bwi/PersonalNotes/PersonalNotesDefaultService.swift b/bwi/PersonalNotes/PersonalNotesDefaultService.swift index b6f9023b4..5f3a4fc81 100644 --- a/bwi/PersonalNotes/PersonalNotesDefaultService.swift +++ b/bwi/PersonalNotes/PersonalNotesDefaultService.swift @@ -90,7 +90,7 @@ import MatrixSDK } extension PersonalNotesDefaultService : PersonalNotesService { - + func personalNotesRoomId() -> String? { guard let personalNotesRoom = self.personalNotesRoom() else { return nil @@ -123,8 +123,8 @@ extension PersonalNotesDefaultService : PersonalNotesService { room.replaceTag(PersonalNotesDefaultService.roomTag, with: PersonalNotesDefaultService.roomTag, withOrder: tagOrder) { (response) in } - case .failure(let error): - MXLog.error("**BWI** Personal Notes creation failure:") + case .failure(_ ): + MXLog.error("**BWI** Personal Notes creation failure") } } } @@ -138,4 +138,16 @@ extension PersonalNotesDefaultService : PersonalNotesService { func avatarImageUrl() -> String { return self.defaultAvatar } + + func setAsFavoriteIfNeeded() { + if let room = self.session.room(withRoomId: self.personalNotesRoomId()) { + if room.accountData.tags[kMXRoomTagFavourite] == nil { + let tagOrder = self.session.tagOrderToBe(at: 0, from: UInt(NSNotFound), withTag: kMXRoomTagFavourite) ?? "" + room.replaceTag(kMXRoomTagFavourite, with: kMXRoomTagFavourite, withOrder: tagOrder) { (response) in + let tags = room.accountData.tags + print(tags) + } + } + } + } } diff --git a/bwi/PersonalNotes/PersonalNotesService.swift b/bwi/PersonalNotes/PersonalNotesService.swift index 64a92d80f..5b33efca5 100644 --- a/bwi/PersonalNotes/PersonalNotesService.swift +++ b/bwi/PersonalNotes/PersonalNotesService.swift @@ -24,4 +24,6 @@ protocol PersonalNotesService { func resetPersonalNotesRoom() func avatarImageUrl() -> String + + func setAsFavoriteIfNeeded() } From 546ebf6d81d89ad1d5416b5226302e2f91a46ac8 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Thu, 7 Sep 2023 16:33:23 +0200 Subject: [PATCH 55/82] MESSENGER 4955 disable WYSIWYG commands --- Config/BWIBuildSettings.swift | 1 + .../WYSIWYGInputToolbar/WysiwygInputToolbarView.swift | 3 ++- .../Service/CompletionSuggestionService.swift | 7 ++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index f4e97436a..b2d48874f 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -659,6 +659,7 @@ class BWIBuildSettings: NSObject { // MARK: WYSIWYG var enableFeatureWYSIWYGByDefault = true + var enableWYSIWYGCommands = false // MARK: itunes var itunesAppLink = "" diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift index 60b6edf94..f3b4d0bb7 100644 --- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift +++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift @@ -379,7 +379,8 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp } private func sendWysiwygMessage(content: WysiwygComposerContent) { - if content.markdown.prefix(while: { $0 == "/" }).count == 1 { + // bwi: #4955 disable WYSIWYG commands + if BWIBuildSettings.shared.enableWYSIWYGCommands && content.markdown.prefix(while: { $0 == "/" }).count == 1 { let commandText: String if content.markdown.hasPrefix(MXKSlashCommand.emote.cmd) { // `/me` command works with markdown content diff --git a/RiotSwiftUI/Modules/Room/CompletionSuggestion/Service/CompletionSuggestionService.swift b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Service/CompletionSuggestionService.swift index 5ded36c2c..c52af9506 100644 --- a/RiotSwiftUI/Modules/Room/CompletionSuggestion/Service/CompletionSuggestionService.swift +++ b/RiotSwiftUI/Modules/Room/CompletionSuggestion/Service/CompletionSuggestionService.swift @@ -128,7 +128,12 @@ class CompletionSuggestionService: CompletionSuggestionServiceProtocol { items.send([]) currentTextTriggerSubject.send(nil) case .slash: - currentTextTriggerSubject.send(TextTrigger(key: .slash, text: suggestionPattern.text)) + // bwi: #4955 disable WYSIWYG commands + if BWIBuildSettings.shared.enableWYSIWYGCommands { + currentTextTriggerSubject.send(TextTrigger(key: .slash, text: suggestionPattern.text)) + } else { + break + } } } From 031cd8708434f206adfbe4b6829fe038a4ea5fac Mon Sep 17 00:00:00 2001 From: Arnfried Griesert Date: Fri, 8 Sep 2023 06:41:40 +0000 Subject: [PATCH 56/82] MESSENGER-4807 new add room menu --- Config/BWIBuildSettings.swift | 3 + .../BuM-Beta/BWIBuildSettings+BuM-Beta.swift | 3 +- .../Button-new-dark.svg | 17 +++++ .../button_new_dark.imageset/Contents.json | 12 ++++ .../Button-new-light.svg | 17 +++++ .../button_new_light.imageset/Contents.json | 12 ++++ .../AllChats/AllChatsViewController.swift | 63 +++++++++++++++++-- 7 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button-new-dark.svg create mode 100644 Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button-new-light.svg create mode 100644 Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Contents.json diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 30cd1b34e..f7462cc74 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -669,4 +669,7 @@ class BWIBuildSettings: NSObject { // MARK: OIDC var isOIDCEnabled = true + + // MARK: Create Room Menu + var enableAllChatsToolbar = true } diff --git a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift index 023974a5f..1ffd4cb99 100644 --- a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift +++ b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift @@ -39,7 +39,8 @@ extension BWIBuildSettings { enableLabFeatureWYSIWYG = true showMaintenanceInfoMessageType = true ignoreBlockingMaintenance = true - + enableAllChatsToolbar = false + itunesAppLink = "itms://itunes.apple.com/app/bundesmessenger-beta/id1617068656?mt=8" } diff --git a/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button-new-dark.svg b/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button-new-dark.svg new file mode 100644 index 000000000..ef0537d90 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button-new-dark.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Contents.json new file mode 100644 index 000000000..342061223 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Button-new-dark.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button-new-light.svg b/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button-new-light.svg new file mode 100644 index 000000000..f30c515a5 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button-new-light.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Contents.json new file mode 100644 index 000000000..77ef7c0ca --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Button-new-light.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index b0fcfb848..3d526fab7 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -77,6 +77,9 @@ class AllChatsViewController: HomeViewController { private var isOnboardingCoordinatorPreparing: Bool = false + // bwi: 4807 + private var floatingButton: UIButton? + private var theme: Theme { ThemeService.shared().theme } @@ -92,10 +95,11 @@ class AllChatsViewController: HomeViewController { } private func setToolbarHidden(_ isHidden: Bool, animated: Bool) { - UIView.animate(withDuration: animated ? 0.3 : 0) { - self.isToolbarHidden = isHidden + if BWIBuildSettings.shared.enableAllChatsToolbar { + UIView.animate(withDuration: animated ? 0.3 : 0) { + self.isToolbarHidden = isHidden + } } - } // MARK: - SplitViewMasterViewControllerProtocol @@ -144,6 +148,23 @@ class AllChatsViewController: HomeViewController { } else { toolbar.tintColor = theme.colors.accent } + + // bwi: 4807 - hide the toolbar and show a floating button for room create instead + if !BWIBuildSettings.shared.enableAllChatsToolbar { + // no toolbar then use a floating button instead + floatingButton = UIButton(frame: CGRect(x: 50, y: 50, width: 50, height: 50)) + updateFloatingButton() + if let floatingButton = floatingButton { + view.addSubview(floatingButton) + } + + // set constraint to make the floating button stay in the lower right corner + floatingButton?.translatesAutoresizingMaskIntoConstraints = false + floatingButton?.widthAnchor.constraint(equalToConstant: 50).isActive = true + floatingButton?.heightAnchor.constraint(equalToConstant: 50).isActive = true + floatingButton?.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -10).isActive = true + floatingButton?.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -40).isActive = true + } if BWIBuildSettings.shared.roomFiltersToggle { updateNewFilterSearchAndToggleButton() @@ -248,6 +269,12 @@ class AllChatsViewController: HomeViewController { self.toolbar.tintColor = theme.colors.accent } + // bwi: 4807 - hide the toolbar and show a floating button for room create instead + if !BWIBuildSettings.shared.enableAllChatsToolbar { + toolbar.transform = CGAffineTransform(translationX: 0, y: 2 * toolbarHeight) + self.view.layoutIfNeeded() + } + if BWIBuildSettings.shared.roomFiltersToggle { if ThemeService.shared().isCurrentThemeDark() { roomFilterButton?.setImage(Asset.Images.roomFilterToggleDarkOff.image, for: .normal) @@ -668,6 +695,17 @@ class AllChatsViewController: HomeViewController { } else { self.navigationController?.toolbar?.tintColor = theme.colors.accent } + + // bwi: 4807 + updateFloatingButton() + } + + private func updateFloatingButton() { + if ThemeService.shared().isCurrentThemeDark() { + floatingButton?.setImage(Asset.Images.buttonNewDark.image, for: .normal) + } else { + floatingButton?.setImage(Asset.Images.buttonNewLight.image, for: .normal) + } } // MARK: - Private @@ -699,9 +737,22 @@ class AllChatsViewController: HomeViewController { self.title = currentSpace?.summary?.displayName ?? VectorL10n.allChatsTitle setupEditOptions() - updateToolbar(with: editActionProvider.updateMenu(with: mainSession, parentSpace: currentSpace, completion: { [weak self] menu in - self?.updateToolbar(with: menu) - })) + + let menu = editActionProvider.updateMenu(with: mainSession, parentSpace: currentSpace, completion: { [weak self] menu in + if BWIBuildSettings.shared.enableAllChatsToolbar { + self?.updateToolbar(with: menu) + } else { + } + }) + + if BWIBuildSettings.shared.enableAllChatsToolbar { + updateToolbar(with: menu) + } else { + updateFloatingButton() + self.floatingButton?.menu = menu + self.floatingButton?.showsMenuAsPrimaryAction = true + } + updateEmptyView() updateBadgeButton() } From 7351d7dc48a76725f26397b7090240345edbd20d Mon Sep 17 00:00:00 2001 From: Frank Rotermund Date: Fri, 15 Sep 2023 07:39:41 +0000 Subject: [PATCH 57/82] MESSENGER-4730 personal notes visibility old style --- Config/BWIBuildSettings.swift | 2 +- Podfile.lock | 67 +++++++++++++++---- .../ActionProviders/RoomActionProvider.swift | 11 ++- .../Modules/Settings/SettingsViewController.m | 27 +++++++- Riot/Modules/TabBar/MasterTabBarController.m | 16 ----- .../PersonalNotesDefaultService.swift | 9 +-- 6 files changed, 96 insertions(+), 36 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 43ea7eca4..ca461ed9d 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -530,7 +530,7 @@ class BWIBuildSettings: NSObject { var bwiPersonalNotesRoomLeavable = false var bwiResetPersonalNotesAccountData = false var bwiUseCustomPersonalNotesAvatar = true - var bwiPersonalNotesRoomAsFavorite = true + var bwiPersonalNotesVisibilityInSettings = false var bwiShowTimelineSettings = false diff --git a/Podfile.lock b/Podfile.lock index 2a6591aa6..2254e7cdd 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -20,13 +20,31 @@ PODS: - Down (0.11.0) - DSBottomSheet (0.3.0) - DSWaveformImage (6.1.1) + - DTCoreText (1.6.26): + - DTCoreText/Core (= 1.6.26) + - DTFoundation/Core (~> 1.7.5) + - DTFoundation/DTAnimatedGIF (~> 1.7.5) + - DTFoundation/DTHTMLParser (~> 1.7.5) + - DTFoundation/UIKit (~> 1.7.5) + - DTCoreText/Core (1.6.26): + - DTFoundation/Core (~> 1.7.5) + - DTFoundation/DTAnimatedGIF (~> 1.7.5) + - DTFoundation/DTHTMLParser (~> 1.7.5) + - DTFoundation/UIKit (~> 1.7.5) + - DTFoundation/Core (1.7.18) + - DTFoundation/DTAnimatedGIF (1.7.18) + - DTFoundation/DTHTMLParser (1.7.18): + - DTFoundation/Core + - DTFoundation/UIKit (1.7.18): + - DTFoundation/Core + - DTTJailbreakDetection (0.4.0) - FLEX (4.5.0) - FlowCommoniOS (1.12.2) - GBDeviceInfo (7.1.0): - GBDeviceInfo/Core (= 7.1.0) - GBDeviceInfo/Core (7.1.0) - GZIP (1.3.0) - - Introspect (0.11.0) + - Introspect (0.12.0) - JitsiMeetSDKLite (8.1.2-lite): - JitsiWebRTC (~> 111.0) - JitsiWebRTC (111.0.2) @@ -39,6 +57,9 @@ PODS: - LoggerAPI (1.9.200): - Logging (~> 1.1) - Logging (1.4.0) + - MatomoTracker (7.5.2): + - MatomoTracker/Core (= 7.5.2) + - MatomoTracker/Core (7.5.2) - MatrixSDK (0.27.1): - MatrixSDK/Core (= 0.27.1) - MatrixSDK/Core (0.27.1): @@ -58,7 +79,7 @@ PODS: - OLMKit/olmcpp (= 3.2.12) - OLMKit/olmc (3.2.12) - OLMKit/olmcpp (3.2.12) - - PostHog (2.0.0) + - PostHog (2.0.2) - ReadMoreTextView (3.0.1) - Realm (10.27.0): - Realm/Headers (= 10.27.0) @@ -73,7 +94,7 @@ PODS: - Sentry/Core (7.15.0) - SideMenu (6.5.0) - SwiftBase32 (0.9.0) - - SwiftFormat/CLI (0.50.2) + - SwiftFormat/CLI (0.52.3) - SwiftGen (6.6.2) - SwiftJWT (3.6.200): - BlueCryptor (~> 1.0) @@ -81,7 +102,7 @@ PODS: - BlueRSA (~> 1.0) - KituraContracts (~> 1.2) - LoggerAPI (~> 1.7) - - SwiftLint (0.49.1) + - SwiftLint (0.52.4) - SwiftyBeaver (1.9.5) - UICollectionViewLeftAlignedLayout (1.0.2) - UICollectionViewRightAlignedLayout (0.0.3) @@ -95,6 +116,8 @@ DEPENDENCIES: - Down (~> 0.11.0) - DSBottomSheet (~> 0.3) - DSWaveformImage (~> 6.1.1) + - DTCoreText (= 1.6.26) + - DTTJailbreakDetection (~> 0.4.0) - FLEX (~> 4.5.0) - FlowCommoniOS (~> 1.12.0) - GBDeviceInfo (~> 7.1.0) @@ -102,8 +125,9 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.27.1) - - MatrixSDK/JingleCallStack (= 0.27.1) + - MatomoTracker (~> 7.5.2) + - MatrixSDK (from `https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk`, tag `v2.10.0_rc2`) + - MatrixSDK/JingleCallStack (from `https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk`, tag `v2.10.0_rc2`) - OLMKit - PostHog (~> 2.0.0) - ReadMoreTextView (~> 3.0.1) @@ -122,7 +146,7 @@ DEPENDENCIES: - ZXingObjC (~> 3.6.5) SPEC REPOS: - trunk: + https://github.com/CocoaPods/Specs.git: - AFNetworking - BlueCryptor - BlueECC @@ -130,6 +154,9 @@ SPEC REPOS: - Down - DSBottomSheet - DSWaveformImage + - DTCoreText + - DTFoundation + - DTTJailbreakDetection - FLEX - FlowCommoniOS - GBDeviceInfo @@ -144,7 +171,7 @@ SPEC REPOS: - libPhoneNumber-iOS - LoggerAPI - Logging - - MatrixSDK + - MatomoTracker - MatrixSDKCrypto - OLMKit - PostHog @@ -165,6 +192,16 @@ SPEC REPOS: - zxcvbn-ios - ZXingObjC +EXTERNAL SOURCES: + MatrixSDK: + :git: https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk + :tag: v2.10.0_rc2 + +CHECKOUT OPTIONS: + MatrixSDK: + :git: https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk + :tag: v2.10.0_rc2 + SPEC CHECKSUMS: AFNetworking: 3bd23d814e976cd148d7d44c3ab78017b744cd58 BlueCryptor: b0aee3d9b8f367b49b30de11cda90e1735571c24 @@ -173,11 +210,14 @@ SPEC CHECKSUMS: Down: b6ba1bc985c9d2f4e15e3b293d2207766fa12612 DSBottomSheet: ca0ac37eb5af2dd54663f86b84382ed90a59be2a DSWaveformImage: 3c718a0cf99291887ee70d1d0c18d80101d3d9ce + DTCoreText: ec749e013f2e1f76de5e7c7634642e600a7467ce + DTFoundation: a53f8cda2489208cbc71c648be177f902ee17536 + DTTJailbreakDetection: 5e356c5badc17995f65a83ed9483f787a0057b71 FLEX: e51461dd6f0bfb00643c262acdfea5d5d12c596b FlowCommoniOS: ca92071ab526dc89905495a37844fd7e78d1a7f2 GBDeviceInfo: 5d62fa85bdcce3ed288d83c28789adf1173e4376 GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3 - Introspect: 4cc1e4c34dd016540c8d86a591c231c09dafbee3 + Introspect: b66b675de8a85d9ef832f3a710d8e3c7db186884 JitsiMeetSDKLite: 895213158cf62342069a10634a41d2f1c00057f7 JitsiWebRTC: 80f62908fcf2a1160e0d14b584323fb6e6be630b KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51 @@ -187,20 +227,21 @@ SPEC CHECKSUMS: libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b + MatomoTracker: 1d98ddc58322fd9d65e1a6886b8e41363047bd13 MatrixSDK: f6c197ca06aab29ff69d1105965a57d277dfcd9d MatrixSDKCrypto: 25929a40733b4ab54f659aaf6a730552a0a06504 OLMKit: da115f16582e47626616874e20f7bb92222c7a51 - PostHog: 660ec6c9d80cec17b685e148f17f6785a88b597d + PostHog: f9e5c13ceea86bb5314218c85d16125b797eb332 ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d Realm: 9ca328bd7e700cc19703799785e37f77d1a130f2 Reusable: 6bae6a5e8aa793c9c441db0213c863a64bce9136 Sentry: 63ca44f5e0c8cea0ee5a07686b02e56104f41ef7 SideMenu: f583187d21c5b1dd04c72002be544b555a2627a2 SwiftBase32: 9399c25a80666dc66b51e10076bf591e3bbb8f17 - SwiftFormat: 710117321c55c82675c0dc03055128efbb13c38f + SwiftFormat: 5de81c42f043741a16e17ae2da012bbddc7c0b58 SwiftGen: 1366a7f71aeef49954ca5a63ba4bef6b0f24138c SwiftJWT: 88c412708f58c169d431d344c87bc79a87c830ae - SwiftLint: 32ee33ded0636d0905ef6911b2b67bbaeeedafa5 + SwiftLint: 1cc5cd61ba9bacb2194e340aeb47a2a37fda00b3 SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82 UICollectionViewLeftAlignedLayout: 830bf6fa5bab9f9b464f62e3384f9d2e00b3c0f6 UICollectionViewRightAlignedLayout: 823eef8c567eba4a44c21bc2ffcb0d0d5f361e2d @@ -208,6 +249,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: ce6afe3dea7ea9b073a7ad0406b2cc5615646746 +PODFILE CHECKSUM: e7f4847564a30ed02659a311fe204021498f30e8 COCOAPODS: 1.11.3 diff --git a/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift b/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift index 631103713..2b03d0bb6 100644 --- a/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift +++ b/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift @@ -38,7 +38,7 @@ class RoomActionProvider: RoomActionProviderProtocol { if BWIBuildSettings.shared.bwiFilteredContextMenu { if service.roomId == PersonalNotesDefaultService(mxSession: service.session).personalNotesRoomId() { return UIMenu(children: [ - self.notificationsAction + self.hidePersonalNotesAction ]) } else { return UIMenu(children: [ @@ -110,6 +110,15 @@ class RoomActionProvider: RoomActionProviderProtocol { self.service.isRoomFavourite = !self.service.isRoomFavourite } } + + // bwi #4802 + private var hidePersonalNotesAction: UIAction { + return UIAction( + title: BWIL10n.homeContextMenuPersonalNotes, + image: UIImage(systemName: "star.slash")) { _ in + PersonalNotesSettings().personalNotesVisible = false + } + } private var lowPriorityAction: UIAction { return UIAction( diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index f1922ce57..e16daa0a5 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -172,7 +172,8 @@ typedef NS_ENUM(NSUInteger, USER_INTERFACE) TIMELINE_USER_AVATAR_INDEX, TIMELINE_ENTER_ROOM_INDEX, USER_INTERFACE_LANGUAGE_INDEX, - USER_INTERFACE_TIMELINE_STYLE_INDEX + USER_INTERFACE_TIMELINE_STYLE_INDEX, + PERSONAL_NOTES_INDEX }; typedef NS_ENUM(NSUInteger, TIMELINE) @@ -666,6 +667,9 @@ ChangePasswordCoordinatorBridgePresenterDelegate> //[sectionUserInterface addRowWithTag:USER_INTERFACE_LANGUAGE_INDEX]; [sectionUserInterface addRowWithTag:USER_INTERFACE_THEME_INDEX]; + if (BWIBuildSettings.shared.bwiPersonalNotesVisibilityInSettings) { + [sectionUserInterface addRowWithTag:PERSONAL_NOTES_INDEX]; + } sectionUserInterface = [self updateTimeLine:sectionUserInterface]; [tmpSections addObject: sectionUserInterface]; @@ -2559,6 +2563,9 @@ ChangePasswordCoordinatorBridgePresenterDelegate> } else if (row == USER_INTERFACE_TIMELINE_STYLE_INDEX) { cell = [self buildMessageBubblesCellForTableView:tableView atIndexPath:indexPath]; + } else if (row == PERSONAL_NOTES_INDEX) + { + cell = [self cellForPersonalNotesVisibility:tableView indexPath:indexPath row:row]; } else { cell = [self cellForTimeline:tableView indexPath:indexPath row:row]; } @@ -5408,6 +5415,24 @@ ChangePasswordCoordinatorBridgePresenterDelegate> [self updateSections]; } +- (UITableViewCell*) cellForPersonalNotesVisibility:(UITableView*)tableView indexPath:(NSIndexPath*)indexPath row:(NSInteger)row { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = [BWIL10n bwiSettingsEnableNotesRoomTitle]; + labelAndSwitchCell.mxkSwitch.on = [[[PersonalNotesSettings alloc] init] personalNotesVisible]; + labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePersonalNotesVisibility:) forControlEvents:UIControlEventTouchUpInside]; + + return labelAndSwitchCell; +} + +- (void) togglePersonalNotesVisibility:(UISwitch* )sender { + PersonalNotesSettings* settings = [[PersonalNotesSettings alloc] init]; + settings.personalNotesVisible = sender.on; +} + #pragma mark - ThreadsBetaCoordinatorBridgePresenterDelegate - (void)threadsBetaCoordinatorBridgePresenterDelegateDidTapEnable:(ThreadsBetaCoordinatorBridgePresenter *)coordinatorBridgePresenter diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index 3cdd0bfb9..22614bf80 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -1079,22 +1079,6 @@ if (BWIBuildSettings.shared.bwiPersonalNotesRoom) { [service createPersonalNotesRoomIfNeeded]; } - if (BWIBuildSettings.shared.bwiUseCustomPersonalNotesAvatar) { - MXSession *mainSession = mxSessionArray.firstObject; - - if (mainSession) { - for (MXRoom* room in [mainSession roomsWithTag:PersonalNotesDefaultService.roomTag]) { - if ([room.summary.avatar length] > 0) { - [room setAvatar:@"" success:nil failure:nil]; - } - } - - MXRoom* room = [mainSession roomWithRoomId:[service personalNotesRoomId]]; - if (room && [room.summary.avatar length] > 0) { - [room setAvatar:@"" success:nil failure:nil]; - } - } - } } #pragma mark - bwi matomo tracking diff --git a/bwi/PersonalNotes/PersonalNotesDefaultService.swift b/bwi/PersonalNotes/PersonalNotesDefaultService.swift index 5f3a4fc81..67a222e11 100644 --- a/bwi/PersonalNotes/PersonalNotesDefaultService.swift +++ b/bwi/PersonalNotes/PersonalNotesDefaultService.swift @@ -119,8 +119,8 @@ extension PersonalNotesDefaultService : PersonalNotesService { case .success(let room): _ = self.setRoomId(roomId: room.roomId) - let tagOrder = self.session.tagOrderToBe(at: 0, from: UInt(NSNotFound), withTag: PersonalNotesDefaultService.roomTag) ?? "" - room.replaceTag(PersonalNotesDefaultService.roomTag, with: PersonalNotesDefaultService.roomTag, withOrder: tagOrder) { (response) in + let tagOrder = self.session.tagOrderToBe(at: 0, from: UInt(NSNotFound), withTag: kMXRoomTagFavourite) ?? "" + room.replaceTag(kMXRoomTagFavourite, with: kMXRoomTagFavourite, withOrder: tagOrder) { (response) in } case .failure(_ ): @@ -144,8 +144,9 @@ extension PersonalNotesDefaultService : PersonalNotesService { if room.accountData.tags[kMXRoomTagFavourite] == nil { let tagOrder = self.session.tagOrderToBe(at: 0, from: UInt(NSNotFound), withTag: kMXRoomTagFavourite) ?? "" room.replaceTag(kMXRoomTagFavourite, with: kMXRoomTagFavourite, withOrder: tagOrder) { (response) in - let tags = room.accountData.tags - print(tags) + // check old settings + let layoutSettings = AllChatsLayoutSettingsManager.shared.allChatLayoutSettings + PersonalNotesSettings().personalNotesVisible = layoutSettings.sections.contains(.bwiPersonalNotes) } } } From 6d532fec9fbd765fdf67738665b1ef2fc5c25307 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Fri, 15 Sep 2023 10:05:53 +0200 Subject: [PATCH 58/82] MESSENGER-5133 fix crash creating a dm --- .../Common/Avatar/Service/MatrixSDK/AvatarService.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RiotSwiftUI/Modules/Common/Avatar/Service/MatrixSDK/AvatarService.swift b/RiotSwiftUI/Modules/Common/Avatar/Service/MatrixSDK/AvatarService.swift index ea4488244..743fec34f 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/Service/MatrixSDK/AvatarService.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/Service/MatrixSDK/AvatarService.swift @@ -29,10 +29,10 @@ class AvatarService: AvatarServiceProtocol { static let mimeType = "image/jpeg" static let thumbnailMethod = MXThumbnailingMethodCrop } + // bwi: #5133 fix crash: make mediaManager optional + private let mediaManager: MXMediaManager? - private let mediaManager: MXMediaManager - - init(mediaManager: MXMediaManager) { + init(mediaManager: MXMediaManager?) { self.mediaManager = mediaManager } @@ -59,7 +59,7 @@ class AvatarService: AvatarServiceProtocol { promise(.success(imageUp)) } - self.mediaManager.downloadThumbnail( + self.mediaManager?.downloadThumbnail( fromMatrixContentURI: mxContentUri, withType: Constants.mimeType, inFolder: nil, From 2866d841e347dfdd0aef6e639678dafbefc2c854 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Fri, 15 Sep 2023 11:35:08 +0200 Subject: [PATCH 59/82] MESSENGER-4857 translate invalid password message --- Riot/Assets/de.lproj/Bwi.strings | 1 + Riot/Assets/en.lproj/Bwi.strings | 1 + .../ErrorPresentation/MXKErrorPresentableBuilder.m | 10 ++++++++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 45fd12cf6..d16096b0b 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -506,6 +506,7 @@ "bwi_error_invite_banned_in_room" = "%@ ist vom Raum gebannt."; "bwi_error_invite_general" = "%@ konnte nicht eingeladen werden."; "bwi_error_logout_offline" = "Abmelden ist ohne Internetverbindung nicht möglich."; +"bwi_error_invalid_password" = "Falsches Passwort"; // MARK: - Matomo diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index d85825e88..b9a5dc061 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -415,6 +415,7 @@ "bwi_error_invite_banned_in_room" = "%@ is banned from the room."; "bwi_error_invite_general" = "%@ could not be invited."; "bwi_error_logout_offline" = "Logout not possible without internet connection."; +"bwi_error_invalid_password" = "Invalid password"; // MARK: - Matomo diff --git a/Riot/Modules/MatrixKit/Utils/ErrorPresentation/MXKErrorPresentableBuilder.m b/Riot/Modules/MatrixKit/Utils/ErrorPresentation/MXKErrorPresentableBuilder.m index 077fb7001..c4119f883 100644 --- a/Riot/Modules/MatrixKit/Utils/ErrorPresentation/MXKErrorPresentableBuilder.m +++ b/Riot/Modules/MatrixKit/Utils/ErrorPresentation/MXKErrorPresentableBuilder.m @@ -41,8 +41,14 @@ } else { - title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; - message = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; + // bwi: #4857 translate invalid password error message + if ([error.domain isEqualToString:kMXNSErrorDomain] && [[error.userInfo valueForKey:NSLocalizedDescriptionKey] isEqualToString: @"Invalid username or password"]) { + title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; + message = [BWIL10n bwiErrorInvalidPassword]; + } else { + title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; + message = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; + } if (!title) { From 7a2d1cb5da48bc8ba502ffedde7e63c866ba6e80 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Fri, 15 Sep 2023 12:15:22 +0200 Subject: [PATCH 60/82] fix pipeline --- Riot/Generated/BWIStrings.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index 7c7d639fb..ae7f13208 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -15,6 +15,10 @@ public class BWIL10n: NSObject { public static var allChatsEditLayoutShowPersonalNotes: String { return BWIL10n.tr("Bwi", "all_chats_edit_layout_show_personal_notes") } + /// Suche + public static var allChatsSearchbarPrompt: String { + return BWIL10n.tr("Bwi", "all_chats_searchbar_prompt") + } /// Passwort vergessen? public static var authForgotPassword: String { return BWIL10n.tr("Bwi", "auth_forgot_password") @@ -207,6 +211,10 @@ public class BWIL10n: NSObject { public static var bwiEditPersonalStateTitle: String { return BWIL10n.tr("Bwi", "bwi_edit_personal_state_title") } + /// Falsches Passwort + public static var bwiErrorInvalidPassword: String { + return BWIL10n.tr("Bwi", "bwi_error_invalid_password") + } /// %@ ist bereits im Raum. public static func bwiErrorInviteAlreadyInRoom(_ p1: String) -> String { return BWIL10n.tr("Bwi", "bwi_error_invite_already_in_room", p1) @@ -739,6 +747,10 @@ public class BWIL10n: NSObject { public static var e2eKeyBackupWrongVersion: String { return BWIL10n.tr("Bwi", "e2e_key_backup_wrong_version") } + /// Notizen ausblenden + public static var homeContextMenuPersonalNotes: String { + return BWIL10n.tr("Bwi", "home_context_menu_personal_notes") + } /// Die Verbindung zum Heimserver ist fehlgeschlagen. public static var homeserverConnectionLost: String { return BWIL10n.tr("Bwi", "homeserver_connection_lost") @@ -1019,7 +1031,7 @@ public class BWIL10n: NSObject { public static var pollParticipantDetailsTitle: String { return BWIL10n.tr("Bwi", "poll_participant_details_title") } - /// Stimmen anzeigen + /// Stimmen ansehen public static var pollTimelineShowParticipantsButton: String { return BWIL10n.tr("Bwi", "poll_timeline_show_participants_button") } From fbcd6dcd8c2ece0b608f433c5a397a2a7f28eef4 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Fri, 15 Sep 2023 13:08:07 +0200 Subject: [PATCH 61/82] MESSENGER-4798 change date format for poll details --- .../PollParticipantDetailsModels.swift | 11 +++++++++-- .../PollParticipantDetailsView.swift | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/bwi/PollParticipantDetails/PollParticipantDetailsModels.swift b/bwi/PollParticipantDetails/PollParticipantDetailsModels.swift index 2a8f11599..a4ea25ae5 100644 --- a/bwi/PollParticipantDetails/PollParticipantDetailsModels.swift +++ b/bwi/PollParticipantDetails/PollParticipantDetailsModels.swift @@ -49,8 +49,15 @@ struct PollParticipantVoter: Identifiable, BindableState { let dateFormatter = DateFormatter() dateFormatter.timeZone = TimeZone.current dateFormatter.calendar = Calendar.current - dateFormatter.dateFormat = "dd. MMMM, yyyy HH:mm" - let strDate = dateFormatter.string(from: votingTime) + var strDate = "" + if Calendar.current.isDateInToday(votingTime) { + dateFormatter.dateFormat = "HH:mm" + strDate = "Heute, ".appending(dateFormatter.string(from: votingTime)) + } else { + dateFormatter.dateFormat = "E, d. MMM yyyy, HH:mm" + strDate = dateFormatter.string(from: votingTime) + } + strDate.append(contentsOf: " Uhr") return PollParticipantVoter(displayName: user.displayname, userAvatarData: avatarData, formattedVotingTime: strDate) } else { diff --git a/bwi/PollParticipantDetails/PollParticipantDetailsView.swift b/bwi/PollParticipantDetails/PollParticipantDetailsView.swift index 7b9bb6c4a..0990f411a 100644 --- a/bwi/PollParticipantDetails/PollParticipantDetailsView.swift +++ b/bwi/PollParticipantDetails/PollParticipantDetailsView.swift @@ -86,6 +86,7 @@ struct PollParticipantVoterView: View { .font(theme.fonts.footnote) .foregroundColor(theme.colors.secondaryContent) } + Spacer() } } } From 6fed37b6354240324a2f2bcb2c8603e9ba1deb59 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Fri, 15 Sep 2023 13:46:16 +0200 Subject: [PATCH 62/82] add localized strings --- Riot/Assets/de.lproj/Bwi.strings | 2 ++ Riot/Assets/en.lproj/Bwi.strings | 2 ++ Riot/Generated/BWIStrings.swift | 8 ++++++++ .../PollParticipantDetailsModels.swift | 4 ++-- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index d16096b0b..c4fa978f1 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -550,6 +550,8 @@ "poll_timeline_show_participants_button" = "Stimmen ansehen"; "poll_participant_details_show_more" = "Alle ansehen (%lu weitere)"; "poll_participant_details_title" = "Umfragedetails"; +"poll_participant_details_clock_string" = " Uhr"; +"poll_participant_details_date_string_today" = "Heute, "; // MARK: - Welcome Experience "welcome_experience_title1" = "Willkommen beim BundesMessenger"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index b9a5dc061..5fa2458fa 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -455,6 +455,8 @@ "poll_timeline_show_participants_button" = "Show votes"; "poll_participant_details_show_more" = "Show all (%lu more)"; "poll_participant_details_title" = "Poll details"; +"poll_participant_details_clock_string" = ""; +"poll_participant_details_date_string_today" = "Today, "; // MARK: - Welcome Experience "welcome_experience_title1" = "Welcome to BundesMessenger"; diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index ae7f13208..2f62438b4 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -1023,6 +1023,14 @@ public class BWIL10n: NSObject { public static var pollEditFormPollTypeOpen: String { return BWIL10n.tr("Bwi", "poll_edit_form_poll_type_open") } + /// Uhr + public static var pollParticipantDetailsClockString: String { + return BWIL10n.tr("Bwi", "poll_participant_details_clock_string") + } + /// Heute, + public static var pollParticipantDetailsDateStringToday: String { + return BWIL10n.tr("Bwi", "poll_participant_details_date_string_today") + } /// Alle ansehen (%lu weitere) public static func pollParticipantDetailsShowMore(_ p1: Int) -> String { return BWIL10n.tr("Bwi", "poll_participant_details_show_more", p1) diff --git a/bwi/PollParticipantDetails/PollParticipantDetailsModels.swift b/bwi/PollParticipantDetails/PollParticipantDetailsModels.swift index a4ea25ae5..192b0363d 100644 --- a/bwi/PollParticipantDetails/PollParticipantDetailsModels.swift +++ b/bwi/PollParticipantDetails/PollParticipantDetailsModels.swift @@ -52,12 +52,12 @@ struct PollParticipantVoter: Identifiable, BindableState { var strDate = "" if Calendar.current.isDateInToday(votingTime) { dateFormatter.dateFormat = "HH:mm" - strDate = "Heute, ".appending(dateFormatter.string(from: votingTime)) + strDate = BWIL10n.pollParticipantDetailsDateStringToday.appending(dateFormatter.string(from: votingTime)) } else { dateFormatter.dateFormat = "E, d. MMM yyyy, HH:mm" strDate = dateFormatter.string(from: votingTime) } - strDate.append(contentsOf: " Uhr") + strDate.append(contentsOf: BWIL10n.pollParticipantDetailsClockString) return PollParticipantVoter(displayName: user.displayname, userAvatarData: avatarData, formattedVotingTime: strDate) } else { From 099b861a8dcc7ce380c9d109ee9dcd08bfa2f434 Mon Sep 17 00:00:00 2001 From: Arnfried Griesert Date: Fri, 15 Sep 2023 12:13:19 +0000 Subject: [PATCH 63/82] MESSENGER-4951 Changed password confirmation --- Config/BWIBuildSettings.swift | 3 +++ Riot/Assets/de.lproj/Bwi.strings | 2 +- Riot/Assets/en.lproj/Bwi.strings | 2 +- .../ChangePassword/ChangePasswordModels.swift | 2 ++ .../ChangePasswordViewModel.swift | 12 +++++++--- .../ChangePasswordViewModelProtocol.swift | 2 +- .../ChangePasswordCoordinator.swift | 24 ++++++++++++++----- 7 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 3fb3b6b4a..cf7afe206 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -672,6 +672,9 @@ class BWIBuildSettings: NSObject { // MARK: OIDC var isOIDCEnabled = true + + // MARK: Change Password + var showPasswordChangedConfirmation = true // MARK: Create Room Menu var enableAllChatsToolbar = true diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index c4fa978f1..de7b8d186 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -198,7 +198,7 @@ "settings_old_password" = "Aktuelles Passwort"; "settings_new_password" = "Neues Passwort"; "settings_confirm_password" = "Neues Passwort bestätigen"; -"settings_password_updated" = "Dein Passwort wurde aktualisiert"; +"settings_password_changed" = "Dein Passwort wurde erfolgreich geändert."; "settings_password_condition" = "Regeln: Mindestlänge 8 Zeichen, mind. 1 Sonderzeichen, 1 Groß- und 1 Kleinbuchstaben und 1 Ziffer"; "settings_password_too_short_message" = "Das Passwort muss aus mindestens 8 Zeichen bestehen"; "settings_password_has_no_digit" = "Das Passwort muss mindestens eine Ziffer enthalten"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index 5fa2458fa..0de08e92b 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -154,7 +154,7 @@ "settings_new_password" = "new password"; "settings_confirm_password" = "confirm password"; "settings_fail_to_update_password" = "Fail to update password"; -"settings_password_updated" = "Your password has been updated"; +"settings_password_changed" = "Your password has been changed."; "settings_password_condition" = "Rules: Minimum length 8 characters, at least 1 special character, 1 uppercase and 1 lowercase letter and 1 number"; "settings_password_too_short_message" = "The password must be at least 8 characters long"; "settings_password_has_no_digit" = "The password must include at least one digit"; diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordModels.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordModels.swift index 6796dac14..5a4a27606 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordModels.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordModels.swift @@ -68,6 +68,8 @@ enum ChangePasswordViewAction { } enum ChangePasswordErrorType: Hashable { + /// The password has been changed on the server + case passwordChangeSucceeded /// An error response from the homeserver. case mxError(String) /// User entered new passwords do not match diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift index 6cfcea0e6..e9d0125c8 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModel.swift @@ -49,19 +49,25 @@ class ChangePasswordViewModel: ChangePasswordViewModelType, ChangePasswordViewMo switch viewAction { case .submit: guard state.bindings.newPassword1 == state.bindings.newPassword2 else { - Task { await displayError(.passwordsDontMatch) } + Task { await displayError(.passwordsDontMatch, primaryButtonCallback: {}) } return } Task { await callback?(.submit(oldPassword: state.bindings.oldPassword, newPassword: state.bindings.newPassword1, - signoutAllDevices: state.bindings.signoutAllDevices)) } + signoutAllDevices: state.bindings.signoutAllDevices)) + } case .toggleSignoutAllDevices: state.bindings.signoutAllDevices.toggle() } } - @MainActor func displayError(_ type: ChangePasswordErrorType) { + @MainActor func displayError(_ type: ChangePasswordErrorType, primaryButtonCallback: @escaping (() -> Void)) { switch type { + case .passwordChangeSucceeded: + state.bindings.alertInfo = AlertInfo(id: .passwordChangeSucceeded, + title: BWIL10n.settingsPasswordChanged, + message: "", + primaryButton: (VectorL10n.ok, action: { primaryButtonCallback() })) case .mxError(let message): state.bindings.alertInfo = AlertInfo(id: type, title: VectorL10n.error, diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModelProtocol.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModelProtocol.swift index db6848c9c..1c9ce23d9 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/ChangePasswordViewModelProtocol.swift @@ -21,5 +21,5 @@ protocol ChangePasswordViewModelProtocol { var context: ChangePasswordViewModelType.Context { get } /// Display an error to the user. - @MainActor func displayError(_ type: ChangePasswordErrorType) + @MainActor func displayError(_ type: ChangePasswordErrorType, primaryButtonCallback: @escaping (() -> Void)) } diff --git a/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordCoordinator.swift b/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordCoordinator.swift index ef6f8a16d..522b082a3 100644 --- a/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordCoordinator.swift +++ b/RiotSwiftUI/Modules/Settings/ChangePassword/Coordinator/ChangePasswordCoordinator.swift @@ -110,13 +110,18 @@ final class ChangePasswordCoordinator: Coordinator, Presentable { currentTask = Task { [weak self] in do { - try passwordValidator.validate(password: newPassword) - try await parameters.restClient.changePassword(from: oldPassword, to: newPassword, logoutDevices: signoutAllDevices) + try self?.passwordValidator.validate(password: newPassword) + try await self?.parameters.restClient.changePassword(from: oldPassword, to: newPassword, logoutDevices: signoutAllDevices) guard !Task.isCancelled else { return } self?.stopLoading() - self?.callback?() + + if BWIBuildSettings.shared.showPasswordChangedConfirmation { + self?.handleSuccess() + } else { + self?.callback?() + } } catch { self?.stopLoading() self?.handleError(error) @@ -128,14 +133,21 @@ final class ChangePasswordCoordinator: Coordinator, Presentable { @MainActor private func handleError(_ error: Error) { if let mxError = MXError(nsError: error as NSError) { let message = mxError.authenticationErrorMessage() - changePasswordViewModel.displayError(.mxError(message)) + changePasswordViewModel.displayError(.mxError(message), primaryButtonCallback: {}) return } if let error = error as? PasswordValidatorError { - changePasswordViewModel.displayError(.mxError(error.localizedDescription)) + changePasswordViewModel.displayError(.mxError(error.localizedDescription), primaryButtonCallback: {}) } else { - changePasswordViewModel.displayError(.unknown) + changePasswordViewModel.displayError(.unknown, primaryButtonCallback: {}) } } + + // bwi: 4951 - password changed confirmation + @MainActor private func handleSuccess() { + changePasswordViewModel.displayError(.passwordChangeSucceeded, primaryButtonCallback: { [weak self] in + self?.callback?() + }) + } } From 9451100f2f1710f9415e03dc1406d8565dea1332 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Fri, 15 Sep 2023 14:41:58 +0200 Subject: [PATCH 64/82] MESSENGER-5017 translate voice message cancel text --- Riot/Assets/de.lproj/Bwi.strings | 2 +- Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index de7b8d186..09659fa5f 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -407,7 +407,7 @@ "voice_message_remaining_recording_time" = "noch %@s"; "voice_message_stop_locked_mode_recording" = "Klick die Nachricht um zu stoppen oder zu hören"; "voice_message_lock_screen_placeholder" = "Sprachnachricht"; -"voice_message_slide_to_cancel" = "Sliden zum Abbrechen"; +"voice_message_slide_to_cancel" = "Wischen zum Abbrechen"; // MARK: - Developer Menu diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift index e26439230..350ce3e94 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift @@ -134,6 +134,8 @@ class VoiceMessageToolbarView: PassthroughView, NibLoadable, Themable, UIGesture self.tapGesture = tapGesture primaryRecordButton.accessibilityLabel = VectorL10n.roomAccessibilityRecordVoiceMessage primaryRecordButton.accessibilityHint = VectorL10n.roomAccessibilityRecordVoiceMessageHint + // bwi: #5017 translate voice message cancel text + slideToCancelLabel.text = BWIL10n.voiceMessageSlideToCancel updateUIWithDetails(VoiceMessageToolbarViewDetails(), animated: false) } From 624edfb413093c836d2959d240a17aa356f64b8b Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Mon, 18 Sep 2023 14:37:34 +0200 Subject: [PATCH 65/82] MESSENGER-4751 use privacy url of well known --- Config/BWIBuildSettings.swift | 1 - .../AllChats/AllChatsViewController.swift | 12 +++++- .../Modules/Settings/SettingsViewController.m | 4 +- Riot/Modules/TabBar/MasterTabBarController.m | 6 ++- .../Login/AuthenticationLoginModels.swift | 7 +--- .../View/AuthenticationLoginScreen.swift | 39 ++++++++++++------- 6 files changed, 42 insertions(+), 27 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index cf7afe206..cdaa252e3 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -266,7 +266,6 @@ class BWIBuildSettings: NSObject { // Note: Set empty strings to hide the related entry in application settings var applicationCopyrightUrlString = "https://messenger.bwi.de/copyright" - var applicationPrivacyPolicyUrlString = "https://messenger.bwi.de/datenschutz" var applicationTermsConditionsUrlString = "" var applicationPrivacyPolicyWithMatomoSectionUrlString = "https://messenger.bwi.de/datenschutz#c6637" diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index e74cd8e65..d0ab60b04 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -455,8 +455,16 @@ class AllChatsViewController: HomeViewController { alert.addAction(UIAlertAction(title: BWIL10n.bwiAnalyticsAlertInfoButton, style: .default, handler: { [self] action in - if let url = URL(string: BWIBuildSettings.shared.applicationPrivacyPolicyWithMatomoSectionUrlString) { - UIApplication.shared.open(url) + if let defaultURL = URL(string: BWIBuildSettings.shared.applicationPrivacyPolicyWithMatomoSectionUrlString) { + guard let wellKnownDataPrivacyURL = URL(string: self.mainSession.homeserverWellknown.dataPrivacyURL() ?? ""), let defaultHost = defaultURL.host else { + UIApplication.shared.open(defaultURL) + return + } + if !wellKnownDataPrivacyURL.absoluteString.contains(defaultHost) { + UIApplication.shared.open(wellKnownDataPrivacyURL) + } else { + UIApplication.shared.open(defaultURL) + } } showMatomoConsentAlertOnCloseModal = true })) diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index e16daa0a5..049844414 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -759,7 +759,7 @@ ChangePasswordCoordinatorBridgePresenterDelegate> { [sectionAbout addRowWithTag:ABOUT_ACCEPTABLE_USE_INDEX]; } - if (BWIBuildSettings.shared.applicationPrivacyPolicyUrlString.length) + if (self.mainSession.homeserverWellknown.dataPrivacyURL != nil) { [sectionAbout addRowWithTag:ABOUT_PRIVACY_INDEX]; } @@ -3415,7 +3415,7 @@ ChangePasswordCoordinatorBridgePresenterDelegate> } else if (row == ABOUT_PRIVACY_INDEX) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:BWIBuildSettings.shared.applicationPrivacyPolicyUrlString] options:@{} completionHandler:nil]; + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:self.mainSession.homeserverWellknown.dataPrivacyURL] options:@{} completionHandler:nil]; } else if (row == ABOUT_ACCESSIBILITY_DECLARATION_INDEX) { diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index 22614bf80..220d62bf9 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -1089,8 +1089,10 @@ message:[BWIL10n bwiAnalyticsAlertBody:AppInfo.current.displayName] preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:BWIL10n.bwiAnalyticsAlertInfoButton style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:BWIBuildSettings.shared.applicationPrivacyPolicyUrlString] options:@{} completionHandler:nil]; + [alert addAction:[UIAlertAction actionWithTitle:BWIL10n.bwiAnalyticsAlertInfoButton style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + if (self->mxSessionArray.firstObject.homeserverWellknown.dataPrivacyURL != nil) { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:self->mxSessionArray.firstObject.homeserverWellknown.dataPrivacyURL] options:@{} completionHandler:nil]; + } }]]; [alert addAction:[UIAlertAction actionWithTitle:BWIL10n.bwiAnalyticsAlertCancelButton style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { BWIAnalytics.sharedTracker.running = NO; diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift index 356bd5fd3..e3cd5b4a6 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift @@ -88,11 +88,8 @@ struct AuthenticationLoginViewState: BindableState { hasValidCredentials && !isLoading } - var dataPrivacyString: String { - guard let urlString = AuthenticationService.shared.wellknown?.dataPrivacyURL() else { - return BWIBuildSettings.shared.applicationPrivacyPolicyUrlString - } - return urlString + var dataPrivacyString: String? { + return AuthenticationService.shared.wellknown?.dataPrivacyURL() } } diff --git a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift index 60e1b2d35..fead5cd83 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift @@ -209,22 +209,31 @@ struct AuthenticationLoginScreen: View { } var dataPrivacyForm: some View { - Button(action: { - let tosURL = URL.init(string: viewModel.viewState.dataPrivacyString)! // add your link here - if UIApplication.shared.canOpenURL(tosURL) { - UIApplication.shared.open(tosURL) + VStack() { + if viewModel.viewState.dataPrivacyString != nil { + Button(action: { + guard let urlString = viewModel.viewState.dataPrivacyString else { + return + } + let tosURL = URL.init(string: urlString)! // add your link here + if UIApplication.shared.canOpenURL(tosURL) { + UIApplication.shared.open(tosURL) + } + }, label: { + Text(BWIL10n.authenticationDataprivacyText) + .font(theme.fonts.footnote) + .foregroundColor(theme.colors.primaryContent) + + + Text(BWIL10n.authenticationDataprivacyLink) + .font(theme.fonts.footnote) + .foregroundColor(.blue) + .underline() + }) + .padding([.horizontal], 20) + } else { + EmptyView() } - }, label: { - Text(BWIL10n.authenticationDataprivacyText) - .font(theme.fonts.footnote) - .foregroundColor(theme.colors.primaryContent) - + - Text(BWIL10n.authenticationDataprivacyLink) - .font(theme.fonts.footnote) - .foregroundColor(.blue) - .underline() - }) - .padding([.horizontal], 20) + } } var loginDescription: some View { From 35c7ebade7fdf0dc549743a5a7ee7ec6124b75f6 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Mon, 18 Sep 2023 17:54:49 +0200 Subject: [PATCH 66/82] MESSENGER-4751 add feature flag --- Config/BWIBuildSettings.swift | 4 ++++ Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift | 1 + Config/BuM/BWIBuildSettings+BuM.swift | 1 + .../Home/AllChats/AllChatsViewController.swift | 16 ++++++++++------ Riot/Modules/Settings/SettingsViewController.m | 15 +++++++++++++-- Riot/Modules/TabBar/MasterTabBarController.m | 8 ++++++-- .../Login/AuthenticationLoginModels.swift | 6 +++++- 7 files changed, 40 insertions(+), 11 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index cdaa252e3..13fcd2a60 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -266,9 +266,13 @@ class BWIBuildSettings: NSObject { // Note: Set empty strings to hide the related entry in application settings var applicationCopyrightUrlString = "https://messenger.bwi.de/copyright" + var applicationPrivacyPolicyUrlString = "https://messenger.bwi.de/datenschutz" var applicationTermsConditionsUrlString = "" var applicationPrivacyPolicyWithMatomoSectionUrlString = "https://messenger.bwi.de/datenschutz#c6637" + // (#4751) use privacy policy link of well known + var bwiUseWellKnownPrivacyPolicyLink: Bool = false + // MARk: - Matrix permalinks // Paths for URLs that will considered as Matrix permalinks. Those permalinks are opened within the app var permalinkSupportedHosts: [String: [String]] = [:] diff --git a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift index 24791e993..6cc9f294a 100644 --- a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift +++ b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift @@ -38,6 +38,7 @@ extension BWIBuildSettings { showMaintenanceInfoMessageType = true ignoreBlockingMaintenance = true enableAllChatsToolbar = false + bwiUseWellKnownPrivacyPolicyLink = true itunesAppLink = "itms://itunes.apple.com/app/bundesmessenger-beta/id1617068656?mt=8" } diff --git a/Config/BuM/BWIBuildSettings+BuM.swift b/Config/BuM/BWIBuildSettings+BuM.swift index 1605a0865..90a587b43 100644 --- a/Config/BuM/BWIBuildSettings+BuM.swift +++ b/Config/BuM/BWIBuildSettings+BuM.swift @@ -27,6 +27,7 @@ extension BWIBuildSettings { authScreenShowTestServerOptions = false bwiNotificationTimes = true enableNewSessionManagerByDefault = true + bwiUseWellKnownPrivacyPolicyLink = true itunesAppLink = "itms://itunes.apple.com/app/bundesmessenger/id1616866351?mt=8" } diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index d0ab60b04..94476c631 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -456,12 +456,16 @@ class AllChatsViewController: HomeViewController { style: .default, handler: { [self] action in if let defaultURL = URL(string: BWIBuildSettings.shared.applicationPrivacyPolicyWithMatomoSectionUrlString) { - guard let wellKnownDataPrivacyURL = URL(string: self.mainSession.homeserverWellknown.dataPrivacyURL() ?? ""), let defaultHost = defaultURL.host else { - UIApplication.shared.open(defaultURL) - return - } - if !wellKnownDataPrivacyURL.absoluteString.contains(defaultHost) { - UIApplication.shared.open(wellKnownDataPrivacyURL) + if BWIBuildSettings.shared.bwiUseWellKnownPrivacyPolicyLink { + guard let wellKnownDataPrivacyURL = URL(string: self.mainSession.homeserverWellknown.dataPrivacyURL() ?? ""), let defaultHost = defaultURL.host else { + UIApplication.shared.open(defaultURL) + return + } + if !wellKnownDataPrivacyURL.absoluteString.contains(defaultHost) { + UIApplication.shared.open(wellKnownDataPrivacyURL) + } else { + UIApplication.shared.open(defaultURL) + } } else { UIApplication.shared.open(defaultURL) } diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 049844414..66f8afe94 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -759,7 +759,11 @@ ChangePasswordCoordinatorBridgePresenterDelegate> { [sectionAbout addRowWithTag:ABOUT_ACCEPTABLE_USE_INDEX]; } - if (self.mainSession.homeserverWellknown.dataPrivacyURL != nil) + if (self.mainSession.homeserverWellknown.dataPrivacyURL != nil && BWIBuildSettings.shared.bwiUseWellKnownPrivacyPolicyLink) + { + [sectionAbout addRowWithTag:ABOUT_PRIVACY_INDEX]; + } + else if (BWIBuildSettings.shared.applicationPrivacyPolicyUrlString.length && !BWIBuildSettings.shared.bwiUseWellKnownPrivacyPolicyLink) { [sectionAbout addRowWithTag:ABOUT_PRIVACY_INDEX]; } @@ -3415,7 +3419,14 @@ ChangePasswordCoordinatorBridgePresenterDelegate> } else if (row == ABOUT_PRIVACY_INDEX) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:self.mainSession.homeserverWellknown.dataPrivacyURL] options:@{} completionHandler:nil]; + if (BWIBuildSettings.shared.bwiUseWellKnownPrivacyPolicyLink) + { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:self.mainSession.homeserverWellknown.dataPrivacyURL] options:@{} completionHandler:nil]; + } + else + { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:BWIBuildSettings.shared.applicationPrivacyPolicyUrlString] options:@{} completionHandler:nil]; + } } else if (row == ABOUT_ACCESSIBILITY_DECLARATION_INDEX) { diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index 220d62bf9..72cb22927 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -1090,8 +1090,12 @@ preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:BWIL10n.bwiAnalyticsAlertInfoButton style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - if (self->mxSessionArray.firstObject.homeserverWellknown.dataPrivacyURL != nil) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:self->mxSessionArray.firstObject.homeserverWellknown.dataPrivacyURL] options:@{} completionHandler:nil]; + if (BWIBuildSettings.shared.bwiUseWellKnownPrivacyPolicyLink) { + if (self->mxSessionArray.firstObject.homeserverWellknown.dataPrivacyURL != nil) { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:self->mxSessionArray.firstObject.homeserverWellknown.dataPrivacyURL] options:@{} completionHandler:nil]; + } + } else { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:BWIBuildSettings.shared.applicationPrivacyPolicyUrlString] options:@{} completionHandler:nil]; } }]]; [alert addAction:[UIAlertAction actionWithTitle:BWIL10n.bwiAnalyticsAlertCancelButton style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift index e3cd5b4a6..2c3699a97 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift @@ -89,7 +89,11 @@ struct AuthenticationLoginViewState: BindableState { } var dataPrivacyString: String? { - return AuthenticationService.shared.wellknown?.dataPrivacyURL() + if BWIBuildSettings.shared.bwiUseWellKnownPrivacyPolicyLink { + return AuthenticationService.shared.wellknown?.dataPrivacyURL() + } else { + return BWIBuildSettings.shared.applicationPrivacyPolicyUrlString + } } } From 0856be5c2ddadde40d8ecb9b9c24f7c45ba32352 Mon Sep 17 00:00:00 2001 From: Frank Rotermund Date: Tue, 19 Sep 2023 11:21:12 +0000 Subject: [PATCH 67/82] Feature/3811 appconfig --- Config/BWIBuildSettings.swift | 3 + .../BuM-Beta/BWIBuildSettings+BuM-Beta.swift | 2 +- .../BuM-Open/BWIBuildSettings+BuM-Open.swift | 1 + Config/BuM/BWIBuildSettings+BuM.swift | 1 + .../AuthenticationCoordinator.swift | 22 ++++- .../Onboarding/OnboardingCoordinator.swift | 2 +- RiotNSE/target.yml | 1 + .../AuthenticationServerSelectionScreen.swift | 89 +++---------------- bwi/AppConfig/AppConfigService.swift | 29 ++++-- .../ServerDownTimeService.swift | 8 ++ .../ServerDowntimeDefaultService.swift | 75 ++++++++++++++++ .../View/OnboardingBwiSplashScreen.swift | 61 ++----------- .../WelcomeExperienceView.swift | 56 +++++++++++- 13 files changed, 205 insertions(+), 145 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index cf7afe206..788f509db 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -678,4 +678,7 @@ class BWIBuildSettings: NSObject { // MARK: Create Room Menu var enableAllChatsToolbar = true + + // MARK: App Config + var avoidServerSelectionOnAppConfig = false } diff --git a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift index 24791e993..b863e7cb8 100644 --- a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift +++ b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift @@ -40,6 +40,6 @@ extension BWIBuildSettings { enableAllChatsToolbar = false itunesAppLink = "itms://itunes.apple.com/app/bundesmessenger-beta/id1617068656?mt=8" + avoidServerSelectionOnAppConfig = true } - } diff --git a/Config/BuM-Open/BWIBuildSettings+BuM-Open.swift b/Config/BuM-Open/BWIBuildSettings+BuM-Open.swift index 7ff2e64bc..ad899f84f 100644 --- a/Config/BuM-Open/BWIBuildSettings+BuM-Open.swift +++ b/Config/BuM-Open/BWIBuildSettings+BuM-Open.swift @@ -31,6 +31,7 @@ extension BWIBuildSettings { bwiEnableLoginProtection = false itunesAppLink = "itms://itunes.apple.com/app/bundesmessenger-open/id6450672686?mt=8" + avoidServerSelectionOnAppConfig = true } } diff --git a/Config/BuM/BWIBuildSettings+BuM.swift b/Config/BuM/BWIBuildSettings+BuM.swift index 1605a0865..45510616a 100644 --- a/Config/BuM/BWIBuildSettings+BuM.swift +++ b/Config/BuM/BWIBuildSettings+BuM.swift @@ -28,6 +28,7 @@ extension BWIBuildSettings { bwiNotificationTimes = true enableNewSessionManagerByDefault = true itunesAppLink = "itms://itunes.apple.com/app/bundesmessenger/id1616866351?mt=8" + avoidServerSelectionOnAppConfig = true } } diff --git a/Riot/Modules/Authentication/AuthenticationCoordinator.swift b/Riot/Modules/Authentication/AuthenticationCoordinator.swift index cb10aacfd..05b80ca30 100644 --- a/Riot/Modules/Authentication/AuthenticationCoordinator.swift +++ b/Riot/Modules/Authentication/AuthenticationCoordinator.swift @@ -144,7 +144,12 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc // bwi: force serverselection on test apps if (BWIBuildSettings.shared.authScreenShowTestServerOptions && BWIBuildSettings.shared.bwiLoginFlowLayout) || BWIBuildSettings.shared.bumLoginFlowLayout { - showServerSelectionScreen(for: flow) + // bwi #3811: when there is a valid url from app config don't show server selection + if BWIBuildSettings.shared.avoidServerSelectionOnAppConfig && AppConfigService.shared.isAppConfig { + self.useHomeserver(AppConfigService.shared.serverUrl()) + } else { + showServerSelectionScreen(for: flow) + } } else { do { // Start the flow using the default server (or a provisioning link if set). @@ -267,6 +272,21 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc callback?(.cancel(.register)) } + /// Updates the login flow using the supplied homeserver address, or shows an error when this isn't possible. + @MainActor private func useHomeserver(_ homeserverAddress: String) { + + let homeserverAddress = HomeserverAddress.sanitized(homeserverAddress) + + Task { + do { + try await authenticationService.startFlow(.login, for: homeserverAddress) + showLoginScreen() + } catch { + displayError(message: error.localizedDescription) + } + } + } + // MARK: - Login /// Shows the login screen. diff --git a/Riot/Modules/Onboarding/OnboardingCoordinator.swift b/Riot/Modules/Onboarding/OnboardingCoordinator.swift index 7e17b32ad..ef638a2c2 100644 --- a/Riot/Modules/Onboarding/OnboardingCoordinator.swift +++ b/Riot/Modules/Onboarding/OnboardingCoordinator.swift @@ -189,7 +189,7 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol { } private func showWelcomeExperience() { - let welcomeVC = WelcomeExperienceViewController.makeViewController(completion: { [weak self] in + let welcomeVC = WelcomeExperienceViewController.makeViewController(completion: { [weak self] in if BWIBuildSettings.shared.onboardingEnableNewAuthenticationFlow { self?.startLoading() self?.beginAuthentication(with: .login) { [weak self] in diff --git a/RiotNSE/target.yml b/RiotNSE/target.yml index 8f65bf50d..67e644b4a 100644 --- a/RiotNSE/target.yml +++ b/RiotNSE/target.yml @@ -59,6 +59,7 @@ targets: - path: ../Config/BWIBuildSettings.swift - path: ../Config/BuM/BWIBuildSettings+BuM.swift - path: ../bwi/UserAgent/UserAgentService.swift + - path: ../bwi/LoginProtection/LoginProtectionService.swift - path: ../Riot/Utils/DataProtectionHelper.swift - path: ../Config/CommonConfiguration.swift - path: ../Riot/Experiments/ diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift index 43fc1d1eb..b42eb0eb3 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift @@ -17,10 +17,6 @@ import SwiftUI import AVKit -enum AuthenticationServerSelectionScreenAlertType { - case showServerMaintenanceInfoMessageAlert, showServerMaintenanceDefaultAlert, showInvalidAppVersionAlert, showDowntimeTimeAlert, showInvalidServerAlert -} - struct AuthenticationServerSelectionScreen: View { // MARK: - Properties @@ -39,7 +35,8 @@ struct AuthenticationServerSelectionScreen: View { // bwi #4976 show maintenance alert @State private var isFetchingDowntime = false @State private var showAlert = false - @State private var activeAlert: AuthenticationServerSelectionScreenAlertType = .showInvalidAppVersionAlert + @State private var isInvalidServerAlert = false + @State private var activeAlert: ServerMaintenanceAlertType = .showInvalidAppVersionAlert private var textFieldFooterColor: Color { viewModel.viewState.hasValidationError ? theme.colors.alert : theme.colors.tertiaryContent @@ -98,67 +95,12 @@ struct AuthenticationServerSelectionScreen: View { } } .alert(isPresented: $showAlert, content: { - switch activeAlert { - case .showInvalidAppVersionAlert: - return Alert(title: Text(BWIL10n.bwiOutdatedVersionWarningTitle), - message: Text(BWIL10n.bwiOutdatedVersionWarningMessage(AppInfo.current.displayName)), - dismissButton: .destructive(Text(BWIL10n.bwiOutdatedVersionAppstoreButton), action: { - let iTunesLink = BWIBuildSettings.shared.itunesAppLink - UIApplication.shared.open(URL(string: iTunesLink)!, options: [:], completionHandler: nil) - })) - case .showDowntimeTimeAlert: - if BWIBuildSettings.shared.ignoreBlockingMaintenance && service.isBlocking() { - return Alert( title: Text(""), - message: Text(ServerDowntimeDefaultService().downtimeText()), - primaryButton: .cancel(Text(BWIL10n.blockingDowntimeAlertIgnoreButton)) { - UserDefaults.standard.set(false, forKey: "ServerDownTimeBlockingKey") - service.setManuallyIgnored() - self.submit() - }, - secondaryButton: .destructive(Text(BWIL10n.blockingDowntimeAlertDismissButton)) - ) - - } else { - return Alert(title: Text(BWIL10n.downtimeTitle), - message: Text(ServerDowntimeDefaultService().downtimeText() != "" ? BWIL10n.downtimeDefaultMessage + "\n\n" + ServerDowntimeDefaultService().downtimeText() : BWIL10n.downtimeDefaultMessage), - dismissButton: .destructive(Text(service.isBlocking() ? BWIL10n.blockingDowntimeAlertDismissButton : BWIL10n.downtimeAlertDismissButton)) { - if service.isBlocking() { - return - } else { - self.submit() - } - }) - } - - case .showServerMaintenanceInfoMessageAlert: - if BWIBuildSettings.shared.ignoreBlockingMaintenance && service.isBlocking() { - return Alert( title: Text(""), - message: Text(ServerDowntimeDefaultService().downtimeText()), - primaryButton: .cancel(Text(BWIL10n.blockingDowntimeAlertIgnoreButton)) { - self.submit() - }, - secondaryButton: .destructive(Text(BWIL10n.blockingDowntimeAlertDismissButton)) - ) - - } else { - return Alert(title: Text(""), - message: Text(ServerDowntimeDefaultService().downtimeText()), - dismissButton: .destructive(Text(service.isBlocking() ? BWIL10n.blockingDowntimeAlertDismissButton : BWIL10n.downtimeAlertDismissButton)) { - if service.isBlocking() { - return - } else { - self.submit() - } - }) - } - case .showServerMaintenanceDefaultAlert: - return Alert(title: Text(BWIL10n.downtimeTitle), - message: Text(BWIL10n.downtimeDefaultMessage), - dismissButton: .destructive(Text(BWIL10n.downtimeAlertDismissButton)) { - self.submit() - }) - case .showInvalidServerAlert: + if isInvalidServerAlert { return self.invalidServerAlert() + } else { + return service.alert(alertType: activeAlert) { + self.submit() + } } }) @@ -274,7 +216,7 @@ struct AuthenticationServerSelectionScreen: View { if isHomeserverAddressValid(viewModel.homeserverAddress) { viewModel.send(viewAction: .confirm) } else { - activeAlert = .showInvalidServerAlert + isInvalidServerAlert = true showAlert = true } } @@ -362,18 +304,11 @@ struct AuthenticationServerSelectionScreen: View { } private func showAlertIfNeeded() { - switch service.nextDowntimeStatus() { - case .none, .warning: - self.submit() - break - case .ongoing: - if service.downtimeType() == .adhocMessage { - activeAlert = .showServerMaintenanceInfoMessageAlert - } else { - activeAlert = .showDowntimeTimeAlert - } + if service.showAlert() { + activeAlert = service.alertType() showAlert = true - break + } else { + self.submit() } } } diff --git a/bwi/AppConfig/AppConfigService.swift b/bwi/AppConfig/AppConfigService.swift index fbbbf517d..c3357464d 100644 --- a/bwi/AppConfig/AppConfigService.swift +++ b/bwi/AppConfig/AppConfigService.swift @@ -35,7 +35,7 @@ extension UserDefaults @objcMembers class AppConfigService : NSObject { static let shared = AppConfigService() - private let serverUrlKey = "serverUrl" + private let serverUrlKey = "home_server_url" private let contentScannerKey = "contentScanner" private let pusherUrlKey = "pusherUrl" private let permalinkUrlKey = "permalinkUrl" @@ -50,10 +50,14 @@ extension UserDefaults var session: MXSession? + var isAppConfig: Bool + private func loadAppConfig() { + isAppConfig = false do { if let dataIn = UserDefaults.standard.value(forKey: savedConfig) as? Data { appConfig = try JSONDecoder().decode(AppConfig.self, from: dataIn) + isAppConfig = true } } catch { @@ -70,17 +74,18 @@ extension UserDefaults } private func checkUrlSavety(_ serverUrl: String) -> Bool { - if serverUrl.hasSuffix("bwi.de") - || serverUrl.hasSuffix("example.com") - || serverUrl.hasSuffix("example.com") - || serverUrl.hasSuffix("example.com") { - return true + if BWIBuildSettings.shared.bwiEnableLoginProtection { + let protectionService = LoginProtectionService() + protectionService.hashes = BWIBuildSettings.shared.bwiHashes + + return protectionService.isValid(serverUrl) } else { - return false + return true } } override init() { + isAppConfig = false super.init() self.loadAppConfig() } @@ -93,12 +98,13 @@ extension UserDefaults } func handleAppConfig() { - if let dict = UserDefaults.standard.dictionary(forKey: configKey) { var config = AppConfig() if let serverUrl = dict[serverUrlKey] as? String { - if checkUrlSavety(serverUrl) { + if serverUrl.count == 0 { + config.serverUrl = nil + } else if checkUrlSavety(serverUrl) { config.serverUrl = serverUrl } } @@ -118,6 +124,9 @@ extension UserDefaults } } + // app config needs at least a valid server url + isAppConfig = (config.serverUrl != nil) + if config != appConfig { appConfig = config self.saveAppConfig() @@ -125,6 +134,8 @@ extension UserDefaults completion(true) } } + + UserDefaults.standard.removeObject(forKey: configKey) } } diff --git a/bwi/ServerMaintenance/ServerDownTimeService.swift b/bwi/ServerMaintenance/ServerDownTimeService.swift index 904bd0468..5ae327438 100644 --- a/bwi/ServerMaintenance/ServerDownTimeService.swift +++ b/bwi/ServerMaintenance/ServerDownTimeService.swift @@ -16,6 +16,7 @@ */ import Foundation +import SwiftUI enum ServerDowntimeStatus { case none @@ -23,6 +24,10 @@ enum ServerDowntimeStatus { case ongoing } +enum ServerMaintenanceAlertType { + case showServerMaintenanceInfoMessageAlert, showServerMaintenanceDefaultAlert, showInvalidAppVersionAlert, showDowntimeTimeAlert +} + enum ServerDowntimeType: String { case adhocMessage = "ADHOC_MESSAGE" case maintenance = "MAINTENANCE" @@ -38,4 +43,7 @@ protocol ServerDowntimeService { func isBlocking() -> Bool func setManuallyIgnored() func isManuallyIgnored() -> Bool + func alert( alertType:ServerMaintenanceAlertType, completion: @escaping () -> Void) -> Alert + func showAlert() -> Bool + func alertType() -> ServerMaintenanceAlertType } diff --git a/bwi/ServerMaintenance/ServerDowntimeDefaultService.swift b/bwi/ServerMaintenance/ServerDowntimeDefaultService.swift index 11bba57aa..919e0071b 100644 --- a/bwi/ServerMaintenance/ServerDowntimeDefaultService.swift +++ b/bwi/ServerMaintenance/ServerDowntimeDefaultService.swift @@ -18,6 +18,7 @@ import Foundation import UIKit import MatrixSDK +import SwiftUI fileprivate let BWI_ServerDowntimes = "BWI_ServerDowntimes" @@ -150,6 +151,80 @@ fileprivate let maintenanceURL = "/_matrix/cmaintenance" extension ServerDowntimeDefaultService : ServerDowntimeService { + func alert(alertType: ServerMaintenanceAlertType, completion: @escaping () -> Void) -> Alert { + switch alertType { + case .showInvalidAppVersionAlert: + return Alert(title: Text(BWIL10n.bwiOutdatedVersionWarningTitle), + message: Text(BWIL10n.bwiOutdatedVersionWarningMessage(AppInfo.current.displayName)), + dismissButton: .destructive(Text(BWIL10n.bwiOutdatedVersionAppstoreButton), action: { + let iTunesLink = BWIBuildSettings.shared.itunesAppLink + UIApplication.shared.open(URL(string: iTunesLink)!, options: [:], completionHandler: nil) + })) + case .showDowntimeTimeAlert: + if BWIBuildSettings.shared.ignoreBlockingMaintenance && isBlocking() { + return Alert( title: Text(""), + message: Text(ServerDowntimeDefaultService().downtimeText()), + primaryButton: .cancel(Text(BWIL10n.blockingDowntimeAlertIgnoreButton)) { + UserDefaults.standard.set(false, forKey: "ServerDownTimeBlockingKey") + self.setManuallyIgnored() + completion() + }, + secondaryButton: .destructive(Text(BWIL10n.blockingDowntimeAlertDismissButton)) + ) + + } else { + return Alert(title: Text(BWIL10n.downtimeTitle), + message: Text(ServerDowntimeDefaultService().downtimeText() != "" ? BWIL10n.downtimeDefaultMessage + "\n\n" + ServerDowntimeDefaultService().downtimeText() : BWIL10n.downtimeDefaultMessage), + dismissButton: .destructive(Text(isBlocking() ? BWIL10n.blockingDowntimeAlertDismissButton : BWIL10n.downtimeAlertDismissButton)) { + if self.isBlocking() { + return + } else { + completion() + } + }) + } + case .showServerMaintenanceInfoMessageAlert: + if BWIBuildSettings.shared.ignoreBlockingMaintenance && isBlocking() { + return Alert( title: Text(""), + message: Text(ServerDowntimeDefaultService().downtimeText()), + primaryButton: .cancel(Text(BWIL10n.blockingDowntimeAlertIgnoreButton)) { + completion() + }, + secondaryButton: .destructive(Text(BWIL10n.blockingDowntimeAlertDismissButton)) + ) + + } else { + return Alert(title: Text(""), + message: Text(ServerDowntimeDefaultService().downtimeText()), + dismissButton: .destructive(Text(isBlocking() ? BWIL10n.blockingDowntimeAlertDismissButton : BWIL10n.downtimeAlertDismissButton)) { + if self.isBlocking() { + return + } else { + completion() + } + }) + } + case .showServerMaintenanceDefaultAlert: + return Alert(title: Text(BWIL10n.downtimeTitle), + message: Text(BWIL10n.downtimeDefaultMessage), + dismissButton: .destructive(Text(BWIL10n.downtimeAlertDismissButton)) { + completion() + }) + } + } + + func showAlert() -> Bool { + return nextDowntimeStatus() == .ongoing + } + + func alertType() -> ServerMaintenanceAlertType { + if downtimeType() == .adhocMessage { + return .showServerMaintenanceInfoMessageAlert + } else { + return .showDowntimeTimeAlert + } + } + func fetchDowntimes(session: MXSession, completion: @escaping () -> Void) { session.matrixRestClient.getDowntime(completion: { (jsonResponse, error) in do { diff --git a/bwi/SplashScreen/View/OnboardingBwiSplashScreen.swift b/bwi/SplashScreen/View/OnboardingBwiSplashScreen.swift index ad508412a..d9aa25ce4 100644 --- a/bwi/SplashScreen/View/OnboardingBwiSplashScreen.swift +++ b/bwi/SplashScreen/View/OnboardingBwiSplashScreen.swift @@ -16,10 +16,6 @@ import SwiftUI -enum OnBoardingSplashScreenAlertType { - case showServerMaintenanceInfoMessageAlert, showServerMaintenanceDefaultAlert, showInvalidAppVersionAlert, showDowntimeTimeAlert -} - @available(iOS 14.0, *) /// The splash screen shown at the beginning of the onboarding flow. struct OnboardingBwiSplashScreen: View { @@ -36,7 +32,7 @@ struct OnboardingBwiSplashScreen: View { @State private var overlayFrame: CGRect = .zero @State private var isFetchingDowntime = false @State private var showAlert = false - @State private var activeAlert: OnBoardingSplashScreenAlertType = .showInvalidAppVersionAlert + @State private var activeAlert: ServerMaintenanceAlertType = .showInvalidAppVersionAlert // MARK: Public @@ -79,40 +75,8 @@ struct OnboardingBwiSplashScreen: View { .navigationTitle("") .navigationBarHidden(true) .alert(isPresented: $showAlert, content: { - switch activeAlert { - case .showInvalidAppVersionAlert: - return Alert(title: Text(BWIL10n.bwiOutdatedVersionWarningTitle), - message: Text(BWIL10n.bwiOutdatedVersionWarningMessage(AppInfo.current.displayName)), - dismissButton: .destructive(Text(BWIL10n.bwiOutdatedVersionAppstoreButton), action: { - let iTunesLink = BWIBuildSettings.shared.itunesAppLink - UIApplication.shared.open(URL(string: iTunesLink)!, options: [:], completionHandler: nil) - })) - case .showDowntimeTimeAlert: - return Alert(title: Text(BWIL10n.downtimeTitle), - message: Text(ServerDowntimeDefaultService().downtimeText() != "" ? BWIL10n.downtimeDefaultMessage + "\n\n" + ServerDowntimeDefaultService().downtimeText() : BWIL10n.downtimeDefaultMessage), - dismissButton: .destructive(Text(service.isBlocking() ? BWIL10n.blockingDowntimeAlertDismissButton : BWIL10n.downtimeAlertDismissButton)) { - if service.isBlocking() { - return - } else { - viewModel.send(viewAction: .login) - } - }) - case .showServerMaintenanceInfoMessageAlert: - return Alert(title: Text(""), - message: Text(ServerDowntimeDefaultService().downtimeText()), - dismissButton: .destructive(Text(service.isBlocking() ? BWIL10n.blockingDowntimeAlertDismissButton : BWIL10n.downtimeAlertDismissButton)) { - if service.isBlocking() { - return - } else { - viewModel.send(viewAction: .login) - } - }) - case .showServerMaintenanceDefaultAlert: - return Alert(title: Text(BWIL10n.downtimeTitle), - message: Text(BWIL10n.downtimeDefaultMessage), - dismissButton: .destructive(Text(BWIL10n.downtimeAlertDismissButton)) { - viewModel.send(viewAction: .login) - }) + service.alert(alertType: activeAlert) { + viewModel.send(viewAction: .login) } }) } @@ -146,24 +110,11 @@ struct OnboardingBwiSplashScreen: View { private func showAlertIfNeeded() { - switch service.nextDowntimeStatus() { - case .none: - viewModel.send(viewAction: .login) - break - case .ongoing: - if service.downtimeType() == .adhocMessage { - activeAlert = .showServerMaintenanceInfoMessageAlert - } else { - activeAlert = .showDowntimeTimeAlert - } + if service.showAlert() { + activeAlert = service.alertType() showAlert = true - break - case .warning: - // only show active downtimes + } else { viewModel.send(viewAction: .login) - // showAlert = true - // activeAlert = .showServerMaintenanceAlert - break } } } diff --git a/bwi/WelcomeExperience/WelcomeExperienceView.swift b/bwi/WelcomeExperience/WelcomeExperienceView.swift index 10fe77093..a50e8330f 100644 --- a/bwi/WelcomeExperience/WelcomeExperienceView.swift +++ b/bwi/WelcomeExperience/WelcomeExperienceView.swift @@ -36,6 +36,11 @@ fileprivate struct Page: View { @State var buttonCallback: (() -> Void)? + private let service = ServerDowntimeDefaultService() + @State private var isFetchingDowntime = false + @State private var showAlert = false + @State private var activeAlert: ServerMaintenanceAlertType = .showInvalidAppVersionAlert + var body: some View { GeometryReader { geometry in VStack(alignment: .center, spacing: 40) { @@ -54,8 +59,9 @@ fileprivate struct Page: View { .multilineTextAlignment(.center) Spacer() + // bwi #3811: check for maintenance when the server selection is skipped if let doneButton = doneButton, let buttonCallback = buttonCallback { - Button(action: buttonCallback) { + Button(action: startButtonAction) { Text(doneButton) .foregroundColor(.white) .padding(.vertical, 10) @@ -64,6 +70,11 @@ fileprivate struct Page: View { .clipShape(RoundedRectangle(cornerRadius: 10)) } .padding(.bottom, 100) + .alert(isPresented: $showAlert, content: { + service.alert(alertType: activeAlert) { + buttonCallback() + } + }) } } .padding(.top, 100) @@ -71,6 +82,49 @@ fileprivate struct Page: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } } + + private func startButtonAction() { + guard let buttonCallback = buttonCallback else { + return + } + + if BWIBuildSettings.shared.enableMaintenanceInfoOnLogin && BWIBuildSettings.shared.avoidServerSelectionOnAppConfig && AppConfigService.shared.isAppConfig { + isFetchingDowntime = true // show progresview + + if BWIBuildSettings.shared.useTestDataForDowntime { + service.fetchDowntimes { + self.isFetchingDowntime = false // hide progressview + self.showAlertIfNeeded() + } + } else { + service.fetchDowntimesWithDirectRequest { success in + DispatchQueue.main.async { + self.isFetchingDowntime = false // hide progressview + if success { + self.showAlertIfNeeded() + } else { + + } + } + } + } + } else { + buttonCallback() + } + } + + private func showAlertIfNeeded() { + guard let buttonCallback = buttonCallback else { + return + } + + if service.showAlert() { + activeAlert = service.alertType() + showAlert = true + } else { + buttonCallback() + } + } } struct WelcomeExperienceView_Previews: PreviewProvider { From 4b556e4dc6382aaa184a6c2cd50b58c938ee7a02 Mon Sep 17 00:00:00 2001 From: Arnfried Griesert Date: Tue, 19 Sep 2023 11:59:20 +0000 Subject: [PATCH 68/82] MESSENGER-4565 Added new developer settings options --- Riot/Assets/de.lproj/Bwi.strings | 10 ++ Riot/Assets/en.lproj/Bwi.strings | 10 ++ .../DeveloperSettingsView.swift | 27 ++++++ bwi/DeveloperSettings/MaintenanceView.swift | 73 ++++++++++++++ .../ServerCapabilitiesView.swift | 73 ++++++++++++++ .../ServerSideKeyBackupView.swift | 96 +++++++++++++++++++ .../UserAccountDataView.swift | 96 +++++++++++++++++++ bwi/DeveloperSettings/WellKnownView.swift | 73 ++++++++++++++ bwi/Tools/BWIRestClient.swift | 63 +++++++++++- 9 files changed, 520 insertions(+), 1 deletion(-) create mode 100644 bwi/DeveloperSettings/MaintenanceView.swift create mode 100644 bwi/DeveloperSettings/ServerCapabilitiesView.swift create mode 100644 bwi/DeveloperSettings/ServerSideKeyBackupView.swift create mode 100644 bwi/DeveloperSettings/UserAccountDataView.swift create mode 100644 bwi/DeveloperSettings/WellKnownView.swift diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 09659fa5f..818ff0ad0 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -52,6 +52,16 @@ "bwi_settings_developer_restrict_user" = "Nutzer einschränken"; "bwi_settings_developer_unrestrict_user" = "Nutzereinschränkung aufheben"; "bwi_settings_developer_unmark_banner" = "Banner als nicht gelesen markieren"; +"bwi_settings_developer_well_known" = "Well-Known"; +"bwi_settings_developer_capabilities" = "Capabilities"; +"bwi_settings_developer_maintenance" = "Maintenance"; +"bwi_settings_developer_user_account_data" = "Account Data"; +"bwi_settings_developer_user_account_data_type" = "Type"; +"bwi_settings_developer_key_backup" = "Key Backup"; +"bwi_settings_developer_key_backup_algorithm" = "Algorithmus"; +"bwi_settings_developer_key_backup_count" = "Gespeicherte Schlüssel"; +"bwi_settings_developer_key_backup_etag" = "ETag"; +"bwi_settings_developer_key_backup_version" = "Version"; // MARK: - MDM diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index 0de08e92b..e45e03506 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -53,6 +53,16 @@ "bwi_settings_developer_restrict_user" = "Restrict user"; "bwi_settings_developer_unrestrict_user" = "Remove user restriction"; "bwi_settings_developer_unmark_banner" = "Unmark Feature Banner"; +"bwi_settings_developer_well_known" = "Well-Known"; +"bwi_settings_developer_capabilities" = "Capabilities"; +"bwi_settings_developer_maintenance" = "Maintenance"; +"bwi_settings_developer_user_account_data" = "Account Data"; +"bwi_settings_developer_user_account_data_type" = "Type"; +"bwi_settings_developer_key_backup" = "Key Backup"; +"bwi_settings_developer_key_backup_algorithm" = "Algorithm"; +"bwi_settings_developer_key_backup_count" = "Stored Keys"; +"bwi_settings_developer_key_backup_etag" = "ETag"; +"bwi_settings_developer_key_backup_version" = "Version"; // MARK: - MDM diff --git a/bwi/DeveloperSettings/DeveloperSettingsView.swift b/bwi/DeveloperSettings/DeveloperSettingsView.swift index 7fb41e534..25a790b1e 100644 --- a/bwi/DeveloperSettings/DeveloperSettingsView.swift +++ b/bwi/DeveloperSettings/DeveloperSettingsView.swift @@ -94,6 +94,33 @@ struct DeveloperSettingsView: View { } } } + SwiftUI.Section { + NavigationLink { + WellKnownView(session: session) + } label: { + Text(BWIL10n.bwiSettingsDeveloperWellKnown) + } + NavigationLink { + ServerCapabilitiesView(session: session) + } label: { + Text(BWIL10n.bwiSettingsDeveloperCapabilities) + } + NavigationLink { + MaintenanceView(session: session) + } label: { + Text(BWIL10n.bwiSettingsDeveloperMaintenance) + } + NavigationLink { + UserAccountDataView(session: session) + } label: { + Text(BWIL10n.bwiSettingsDeveloperUserAccountData) + } + NavigationLink { + ServerSideKeyBackupView(session: session) + } label: { + Text(BWIL10n.bwiSettingsDeveloperKeyBackup) + } + } } .listStyle(.grouped) .navigationTitle(BWIL10n.bwiSettingsDeveloper) diff --git a/bwi/DeveloperSettings/MaintenanceView.swift b/bwi/DeveloperSettings/MaintenanceView.swift new file mode 100644 index 000000000..236110943 --- /dev/null +++ b/bwi/DeveloperSettings/MaintenanceView.swift @@ -0,0 +1,73 @@ +// +/* + * Copyright (c) 2022 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import SwiftUI + +struct MaintenanceView: View { + @State var jsonString = "" + var session: MXSession? + + var body: some View { + ScrollView { + VStack(alignment: .leading) { + HStack { + Text(jsonString) + .font(.caption) + .padding() + Spacer() + } + } + } + .navigationTitle(BWIL10n.bwiSettingsDeveloperMaintenance) + .navigationBarTitleDisplayMode(.inline) + .onAppear { + Task { + await fetchData() + } + } + } + + private func fetchData() async { + guard let restClient = session?.matrixRestClient else { + return + } + + let responseDict = await withCheckedContinuation { continuation in + restClient.getDowntime { jsonResponse, error in + continuation.resume(returning: jsonResponse) + } + } + await MainActor.run { + do { + if let responseDict = responseDict { + let data = try JSONSerialization.data(withJSONObject: responseDict as Any, options: [.prettyPrinted]) + jsonString = String(data: data, encoding: .utf8) ?? "" + } else { + jsonString = "" + } + } catch { + jsonString = "" + } + } + } +} + +struct MaintenanceView_Previews: PreviewProvider { + static var previews: some View { + MaintenanceView() + } +} diff --git a/bwi/DeveloperSettings/ServerCapabilitiesView.swift b/bwi/DeveloperSettings/ServerCapabilitiesView.swift new file mode 100644 index 000000000..8b05f48df --- /dev/null +++ b/bwi/DeveloperSettings/ServerCapabilitiesView.swift @@ -0,0 +1,73 @@ +// +/* + * Copyright (c) 2022 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import SwiftUI + +struct ServerCapabilitiesView: View { + @State var jsonString = "" + var session: MXSession? + + var body: some View { + ScrollView { + VStack(alignment: .leading) { + HStack { + Text(jsonString) + .font(.caption) + .padding() + Spacer() + } + } + } + .navigationTitle(BWIL10n.bwiSettingsDeveloperCapabilities) + .navigationBarTitleDisplayMode(.inline) + .onAppear { + Task { + await fetchData() + } + } + } + + private func fetchData() async { + guard let restClient = session?.matrixRestClient else { + return + } + + let responseDict = await withCheckedContinuation { continuation in + restClient.getServerCapabilities { jsonResponse, error in + continuation.resume(returning: jsonResponse) + } + } + await MainActor.run { + do { + if let responseDict = responseDict { + let data = try JSONSerialization.data(withJSONObject: responseDict as Any, options: [.prettyPrinted]) + jsonString = String(data: data, encoding: .utf8) ?? "" + } else { + jsonString = "" + } + } catch { + jsonString = "" + } + } + } +} + +struct ServerCapabilitiesView_Previews: PreviewProvider { + static var previews: some View { + ServerCapabilitiesView() + } +} diff --git a/bwi/DeveloperSettings/ServerSideKeyBackupView.swift b/bwi/DeveloperSettings/ServerSideKeyBackupView.swift new file mode 100644 index 000000000..3a867b6c7 --- /dev/null +++ b/bwi/DeveloperSettings/ServerSideKeyBackupView.swift @@ -0,0 +1,96 @@ +// +/* + * Copyright (c) 2022 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import SwiftUI + +struct ServerSideKeyBackupView: View { + @State var algorithm: String? + @State var count: Int? + @State var etag: String? + @State var version: String? + var session: MXSession? + + var body: some View { + Form { + if let algorithm = algorithm { + TitleAndValueView(title: NSLocalizedString(BWIL10n.bwiSettingsDeveloperKeyBackupAlgorithm, comment: ""), value: algorithm) + } + if let count = count { + TitleAndValueView(title: NSLocalizedString(BWIL10n.bwiSettingsDeveloperKeyBackupCount, comment: ""), value: "\(count)") + } + if let etag = etag { + TitleAndValueView(title: NSLocalizedString(BWIL10n.bwiSettingsDeveloperKeyBackupEtag, comment: ""), value: etag) + } + if let version = version { + TitleAndValueView(title: NSLocalizedString(BWIL10n.bwiSettingsDeveloperKeyBackupVersion, comment: ""), value: version) + } + } + .navigationTitle(BWIL10n.bwiSettingsDeveloperKeyBackup) + .navigationBarTitleDisplayMode(.inline) + .onAppear { + Task { + await fetchData() + } + } + } + + private func fetchData() async { + guard let restClient = session?.matrixRestClient else { + return + } + + let responseDict = await withCheckedContinuation { continuation in + restClient.getKeyBackupVersion { jsonResponse, error in + continuation.resume(returning: jsonResponse) + } + } + await MainActor.run { + if let responseDict = responseDict { + algorithm = responseDict["algorithm"] as? String + count = responseDict["count"] as? Int + etag = responseDict["etag"] as? String + version = responseDict["version"] as? String + } else { + algorithm = nil + count = nil + etag = nil + version = nil + } + } + } + +} + +struct TitleAndValueView: View { + var title: String + var value: String + + var body: some View { + HStack(alignment: .firstTextBaseline) { + Text(title) + Spacer() + Text(value) + .foregroundColor(.secondary) + } + } +} + +struct ServerSideKeyBackupView_Previews: PreviewProvider { + static var previews: some View { + ServerSideKeyBackupView() + } +} diff --git a/bwi/DeveloperSettings/UserAccountDataView.swift b/bwi/DeveloperSettings/UserAccountDataView.swift new file mode 100644 index 000000000..ec423dd99 --- /dev/null +++ b/bwi/DeveloperSettings/UserAccountDataView.swift @@ -0,0 +1,96 @@ +// +/* + * Copyright (c) 2022 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import SwiftUI + +fileprivate let availableAccountDataTypes: [String] = [ + "de.bwi.personal_notes_room", + "de.bwi.should_show_ios_release_notes", + "de.bwi.notification_times", + "de.bwi.top_banner_features", + "de.bwi.notifications", + "im.vector.setting.integration_provisioning", + "im.vector.setting.allowed_widgets" +] + +struct UserAccountDataView: View { + @State var selectedAccountDataType = "de.bwi.personal_notes_room" + @State var jsonString = "" + var session: MXSession? + + var body: some View { + Form { + SwiftUI.Section { + Picker(BWIL10n.bwiSettingsDeveloperUserAccountDataType, selection: $selectedAccountDataType) { + ForEach(availableAccountDataTypes, id:\.self) { type in + Text(type).tag(type) + } + } + .onChange(of: selectedAccountDataType) { newValue in + Task { + await fetchData(forType: newValue) + } + } + } + if !jsonString.isEmpty { + SwiftUI.Section { + Text(jsonString) + .font(.caption) + .padding() + } + } + } + .navigationTitle(BWIL10n.bwiSettingsDeveloperUserAccountData) + .navigationBarTitleDisplayMode(.inline) + .onAppear { + Task { + await fetchData(forType: selectedAccountDataType) + } + } + } + + private func fetchData(forType accountDataType: String) async { + guard let restClient = session?.matrixRestClient else { + return + } + + let responseDict = await withCheckedContinuation { continuation in + restClient.getAccountData(forType: accountDataType) { jsonResponse, error in + continuation.resume(returning: jsonResponse) + } + } + await MainActor.run { + do { + if let responseDict = responseDict { + print(responseDict) + let data = try JSONSerialization.data(withJSONObject: responseDict as Any, options: [.prettyPrinted]) + jsonString = String(data: data, encoding: .utf8) ?? "" + } else { + jsonString = "" + } + } catch { + jsonString = "" + } + } + } +} + +struct UserAccountDataView_Previews: PreviewProvider { + static var previews: some View { + UserAccountDataView() + } +} diff --git a/bwi/DeveloperSettings/WellKnownView.swift b/bwi/DeveloperSettings/WellKnownView.swift new file mode 100644 index 000000000..df63e02eb --- /dev/null +++ b/bwi/DeveloperSettings/WellKnownView.swift @@ -0,0 +1,73 @@ +// +/* + * Copyright (c) 2022 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import SwiftUI + +struct WellKnownView: View { + @State var jsonString = "" + var session: MXSession? + + var body: some View { + ScrollView { + VStack(alignment: .leading) { + HStack { + Text(jsonString) + .font(.caption) + .padding() + Spacer() + } + } + } + .navigationTitle(BWIL10n.bwiSettingsDeveloperWellKnown) + .navigationBarTitleDisplayMode(.inline) + .onAppear { + Task { + await fetchData() + } + } + } + + private func fetchData() async { + guard let restClient = session?.matrixRestClient else { + return + } + + let responseDict = await withCheckedContinuation { continuation in + restClient.getWellKnown { jsonResponse, error in + continuation.resume(returning: jsonResponse) + } + } + await MainActor.run { + do { + if let responseDict = responseDict { + let data = try JSONSerialization.data(withJSONObject: responseDict as Any, options: [.prettyPrinted]) + jsonString = String(data: data, encoding: .utf8) ?? "" + } else { + jsonString = "" + } + } catch { + jsonString = "" + } + } + } +} + +struct WellKnownView_Previews: PreviewProvider { + static var previews: some View { + WellKnownView() + } +} diff --git a/bwi/Tools/BWIRestClient.swift b/bwi/Tools/BWIRestClient.swift index ff71310dc..21b49bae4 100644 --- a/bwi/Tools/BWIRestClient.swift +++ b/bwi/Tools/BWIRestClient.swift @@ -17,7 +17,49 @@ import Foundation public extension MXRestClient { + + @discardableResult + func getWellKnown(completion: @escaping (_ jsonResponse: Dictionary?, _ error: Error?) -> Void) -> MXHTTPOperation? { + + guard let httpClient = httpClient else { + completion(nil, "HTTPClient not available") + return nil + } + + let path = ".well-known/matrix/client" + + return httpClient.request(withMethod: "GET", path: path, parameters: nil, success: { response in + guard response != nil else { + completion(nil, "Response empty") + return + } + completion(response, nil) + }, failure: { (error) in + completion(nil, error) + }) + } + @discardableResult + func getServerCapabilities(completion: @escaping (_ jsonResponse: Dictionary?, _ error: Error?) -> Void) -> MXHTTPOperation? { + + guard let httpClient = httpClient else { + completion(nil, "HTTPClient not available") + return nil + } + + let path = apiPathPrefix + "/capabilities" + + return httpClient.request(withMethod: "GET", path: path, parameters: nil, success: { response in + guard response != nil else { + completion(nil, "Response empty") + return + } + completion(response, nil) + }, failure: { (error) in + completion(nil, error) + }) + } + @discardableResult func getAccountData(forType type: String, completion: @escaping (_ jsonResponse: Dictionary?, _ error: Error?) -> Void) -> MXHTTPOperation? { @@ -73,7 +115,26 @@ public extension MXRestClient { return nil } - let path = "_matrix/cmaintenance" + let path = "/_matrix/cmaintenance" + + return httpClient.request(withMethod: "GET", path: path, parameters: nil, success: { response in + guard response != nil else { + completion(nil, "Response empty") + return + } + completion(response, nil) + }, failure: { (error) in + completion(nil, error) + }) + } + + @discardableResult + func getKeyBackupVersion(completion: @escaping (_ jsonResponse: Dictionary?, _ error: Error?) -> Void) -> MXHTTPOperation? { + guard let httpClient = httpClient else { + return nil + } + + let path = "/_matrix/client/v3/room_keys/version" return httpClient.request(withMethod: "GET", path: path, parameters: nil, success: { response in guard response != nil else { From b8dc1935d03272cc5e23f3ae88348417594ec64b Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Tue, 19 Sep 2023 14:58:08 +0200 Subject: [PATCH 69/82] MESSENGER-4583 optimize nexus upload --- Config/nexus_upload.sh | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Config/nexus_upload.sh b/Config/nexus_upload.sh index 376ca71a0..3fe35ed5d 100755 --- a/Config/nexus_upload.sh +++ b/Config/nexus_upload.sh @@ -3,18 +3,19 @@ NEXUS_BASE_URL=$1 VARIANT=$2 EXT=$3 VERSION=$4 -DATE_STR=$5 -GITHASH=$6 -SUBDIR=$7 +GITTAG=$5 +DATE_STR=$6 +GITHASH=$7 +SUBDIR=$8 -echo "params: $NEXUS_BASE_URL $VARIANT $EXT $VERSION $DATE_STR $GITHASH $SUBDIR" +echo "params: $NEXUS_BASE_URL $VARIANT $EXT $VERSION $GITTAG $DATE_STR $GITHASH $SUBDIR" echo "dir: $PWD" find ../out$SUBDIR -name "*$VARIANT*.$EXT" -print0 | while read -d $'\0' file do echo "Source: $file" zip -r $file.zip $file - BASENAME=$(basename $file .zip)-$VERSION-$DATE_STR-$GITHASH.$EXT$SUBDIR.zip + BASENAME=$(basename $file .zip)-$VERSION-$GITTAG-$DATE_STR-$GITHASH.$EXT$SUBDIR.zip echo "Destination $NEXUS_BASE_URL/$VERSION/$BASENAME" curl --fail -u $S_BWMESSENGER_ID:$S_BWMESSENGER_PASSWORD -v --upload-file $file.zip $NEXUS_BASE_URL/$VERSION/$BASENAME -done \ No newline at end of file +done From 1f00c0c39b3b42c525df555cdfbecb2bebaad2ca Mon Sep 17 00:00:00 2001 From: Frank Rotermund Date: Tue, 19 Sep 2023 15:11:38 +0200 Subject: [PATCH 70/82] MESSENGER-4933 appversion check on login --- .../Login/AuthenticationLoginModels.swift | 2 ++ .../Login/AuthenticationLoginViewModel.swift | 10 ++++++++++ .../Coordinator/AuthenticationLoginCoordinator.swift | 6 +++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift index 2c3699a97..fba209da9 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift @@ -134,4 +134,6 @@ enum AuthenticationLoginErrorType: Hashable { case invalidHomeserver /// The response from the homeserver was unexpected. case unknown + /// Appversion is too old + case appVersion } diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift index 07879621c..c3085dfa0 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift @@ -80,6 +80,16 @@ class AuthenticationLoginViewModel: AuthenticationLoginViewModelType, Authentica message: VectorL10n.authenticationServerSelectionGenericError) case .unknown: state.bindings.alertInfo = AlertInfo(id: type) + case .appVersion: + state.bindings.alertInfo = AlertInfo(id: type, + title: BWIL10n.bwiDeprecatedVersionWarningTitle, + message: BWIL10n.bwiDeprecatedVersionWarningMessage, + primaryButton: (BWIL10n.bwiDeprecatedVersionAppstoreButton, { + + UIApplication.shared.open(URL(string: BWIBuildSettings.shared.itunesAppLink)!) + }), + secondaryButton: (VectorL10n.ok, {})) + } } diff --git a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift index df7f688f4..61d887e69 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift @@ -130,7 +130,11 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { self.showForgotPasswordScreen() } case .login(let username, let password): - self.login(username: username, password: password) + if BWIBuildSettings.shared.bwiCheckAppVersion && ValidAppVersionsDefaultService().isCurrentAppVersionDeprecated() { + authenticationLoginViewModel.displayError(.appVersion) + } else { + self.login(username: username, password: password) + } case .continueWithSSO(let identityProvider): self.callback?(.continueWithSSO(identityProvider)) case .fallback: From c279abb7ab742ca58954440bd84fe10f7d5c0466 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Tue, 19 Sep 2023 15:58:45 +0200 Subject: [PATCH 71/82] MESSENGER-4583 check exit code --- Config/nexus_upload.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Config/nexus_upload.sh b/Config/nexus_upload.sh index 3fe35ed5d..d276552b4 100755 --- a/Config/nexus_upload.sh +++ b/Config/nexus_upload.sh @@ -11,11 +11,21 @@ SUBDIR=$8 echo "params: $NEXUS_BASE_URL $VARIANT $EXT $VERSION $GITTAG $DATE_STR $GITHASH $SUBDIR" echo "dir: $PWD" +check_last_exit_code () { + if [ $1 -ne 0 ]; then + echo "Error: exit code != 0" + exit $1 + fi +} + find ../out$SUBDIR -name "*$VARIANT*.$EXT" -print0 | while read -d $'\0' file +check_last_exit_code $? do echo "Source: $file" zip -r $file.zip $file BASENAME=$(basename $file .zip)-$VERSION-$GITTAG-$DATE_STR-$GITHASH.$EXT$SUBDIR.zip echo "Destination $NEXUS_BASE_URL/$VERSION/$BASENAME" - curl --fail -u $S_BWMESSENGER_ID:$S_BWMESSENGER_PASSWORD -v --upload-file $file.zip $NEXUS_BASE_URL/$VERSION/$BASENAME + curl --fail -v -u $S_BWMESSENGER_ID:$S_BWMESSENGER_PASSWORD --upload-file $file.zip $NEXUS_BASE_URL/$VERSION/$BASENAME + check_last_exit_code $? done +check_last_exit_code $? From 8d9638cdc95cdea66ee7781d1d3709017d2bfef5 Mon Sep 17 00:00:00 2001 From: Arnfried Griesert Date: Wed, 20 Sep 2023 07:44:22 +0000 Subject: [PATCH 72/82] MESSENGER-4807 bum assets floating button --- .../Button-new-dark.svg | 17 ----------------- .../Button_BuM_Dark.svg | 17 +++++++++++++++++ .../BWI/button_new_dark.imageset/Contents.json | 2 +- .../Button-new-light.svg | 17 ----------------- .../Button_BuM_Light.svg | 17 +++++++++++++++++ .../BWI/button_new_light.imageset/Contents.json | 2 +- .../Home/AllChats/AllChatsViewController.swift | 8 ++++---- .../AuthenticationLoginCoordinator.swift | 8 ++++---- 8 files changed, 44 insertions(+), 44 deletions(-) delete mode 100644 Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button-new-dark.svg create mode 100644 Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button_BuM_Dark.svg delete mode 100644 Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button-new-light.svg create mode 100644 Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button_BuM_Light.svg diff --git a/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button-new-dark.svg b/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button-new-dark.svg deleted file mode 100644 index ef0537d90..000000000 --- a/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button-new-dark.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button_BuM_Dark.svg b/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button_BuM_Dark.svg new file mode 100644 index 000000000..0f0658a77 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Button_BuM_Dark.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Contents.json index 342061223..0ed753627 100644 --- a/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/BWI/button_new_dark.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Button-new-dark.svg", + "filename" : "Button_BuM_Dark.svg", "idiom" : "universal" } ], diff --git a/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button-new-light.svg b/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button-new-light.svg deleted file mode 100644 index f30c515a5..000000000 --- a/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button-new-light.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button_BuM_Light.svg b/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button_BuM_Light.svg new file mode 100644 index 000000000..c9f8c0b29 --- /dev/null +++ b/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Button_BuM_Light.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Contents.json b/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Contents.json index 77ef7c0ca..b6b5b9fa7 100644 --- a/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/BWI/button_new_light.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Button-new-light.svg", + "filename" : "Button_BuM_Light.svg", "idiom" : "universal" } ], diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 94476c631..58f0553f1 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -152,7 +152,7 @@ class AllChatsViewController: HomeViewController { // bwi: 4807 - hide the toolbar and show a floating button for room create instead if !BWIBuildSettings.shared.enableAllChatsToolbar { // no toolbar then use a floating button instead - floatingButton = UIButton(frame: CGRect(x: 50, y: 50, width: 50, height: 50)) + floatingButton = UIButton(frame: CGRect(x: 0, y: 0, width: 56, height: 56)) updateFloatingButton() if let floatingButton = floatingButton { view.addSubview(floatingButton) @@ -160,9 +160,9 @@ class AllChatsViewController: HomeViewController { // set constraint to make the floating button stay in the lower right corner floatingButton?.translatesAutoresizingMaskIntoConstraints = false - floatingButton?.widthAnchor.constraint(equalToConstant: 50).isActive = true - floatingButton?.heightAnchor.constraint(equalToConstant: 50).isActive = true - floatingButton?.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -10).isActive = true + floatingButton?.widthAnchor.constraint(equalToConstant: 56).isActive = true + floatingButton?.heightAnchor.constraint(equalToConstant: 56).isActive = true + floatingButton?.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -16).isActive = true floatingButton?.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -40).isActive = true } diff --git a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift index 61d887e69..764a42c21 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift @@ -131,7 +131,7 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { } case .login(let username, let password): if BWIBuildSettings.shared.bwiCheckAppVersion && ValidAppVersionsDefaultService().isCurrentAppVersionDeprecated() { - authenticationLoginViewModel.displayError(.appVersion) + self.authenticationLoginViewModel.displayError(.appVersion) } else { self.login(username: username, password: password) } @@ -195,14 +195,14 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { @MainActor private func handleError(_ error: Error) { if let mxError = MXError(nsError: error as NSError) { let message = mxError.authenticationErrorMessage() - authenticationLoginViewModel.displayError(.mxError(message)) + self.authenticationLoginViewModel.displayError(.mxError(message)) return } if let authenticationError = error as? AuthenticationError { switch authenticationError { case .invalidHomeserver: - authenticationLoginViewModel.displayError(.invalidHomeserver) + self.authenticationLoginViewModel.displayError(.invalidHomeserver) case .loginFlowNotCalled: #warning("Reset the flow") case .missingMXRestClient: @@ -211,7 +211,7 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { return } - authenticationLoginViewModel.displayError(.unknown) + self.authenticationLoginViewModel.displayError(.unknown) } @MainActor private func parseUsername(_ username: String) { From e9b06d8f2ac0564f296905f78862d0e2bd7179a0 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Thu, 21 Sep 2023 14:21:51 +0000 Subject: [PATCH 73/82] MESSENGER-4583 fix pipeline --- Config/nexus_upload.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Config/nexus_upload.sh b/Config/nexus_upload.sh index d276552b4..e0675bd4b 100755 --- a/Config/nexus_upload.sh +++ b/Config/nexus_upload.sh @@ -18,8 +18,12 @@ check_last_exit_code () { fi } -find ../out$SUBDIR -name "*$VARIANT*.$EXT" -print0 | while read -d $'\0' file +echo "checking if file is found" +find ../out$SUBDIR -name "*$VARIANT*.$EXT" | grep . check_last_exit_code $? +echo "find end" + +find ../out$SUBDIR -name "*$VARIANT*.$EXT" -print0 | while read -d $'\0' file do echo "Source: $file" zip -r $file.zip $file From f0959e32a10c1a730e0e04cb76962d05c0bdba33 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Tue, 26 Sep 2023 12:12:12 +0200 Subject: [PATCH 74/82] MESSENGER-4583 remove tag from filename --- Config/nexus_upload.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Config/nexus_upload.sh b/Config/nexus_upload.sh index e0675bd4b..cb997e836 100755 --- a/Config/nexus_upload.sh +++ b/Config/nexus_upload.sh @@ -3,12 +3,11 @@ NEXUS_BASE_URL=$1 VARIANT=$2 EXT=$3 VERSION=$4 -GITTAG=$5 -DATE_STR=$6 -GITHASH=$7 -SUBDIR=$8 +DATE_STR=$5 +GITHASH=$6 +SUBDIR=$7 -echo "params: $NEXUS_BASE_URL $VARIANT $EXT $VERSION $GITTAG $DATE_STR $GITHASH $SUBDIR" +echo "params: $NEXUS_BASE_URL $VARIANT $EXT $VERSION $DATE_STR $GITHASH $SUBDIR" echo "dir: $PWD" check_last_exit_code () { @@ -27,7 +26,7 @@ find ../out$SUBDIR -name "*$VARIANT*.$EXT" -print0 | while read -d $'\0' file do echo "Source: $file" zip -r $file.zip $file - BASENAME=$(basename $file .zip)-$VERSION-$GITTAG-$DATE_STR-$GITHASH.$EXT$SUBDIR.zip + BASENAME=$(basename $file .zip)-$VERSION-$DATE_STR-$GITHASH.$EXT$SUBDIR.zip echo "Destination $NEXUS_BASE_URL/$VERSION/$BASENAME" curl --fail -v -u $S_BWMESSENGER_ID:$S_BWMESSENGER_PASSWORD --upload-file $file.zip $NEXUS_BASE_URL/$VERSION/$BASENAME check_last_exit_code $? From aa25f10d51abfcf6c9e1533c8987e79848db70f2 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Tue, 26 Sep 2023 15:42:06 +0200 Subject: [PATCH 75/82] MESSENGER-5159 update sdk tag --- Podfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Podfile b/Podfile index 6be9cdf9c..6ae6b8b40 100644 --- a/Podfile +++ b/Podfile @@ -43,7 +43,7 @@ when String # specific MatrixSDK released version $matrixSDKVersionSpec = $matrixSDKVersion end -$matrixSDKVersionSpec = { :git => 'https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk', :tag => 'v2.10.0_rc2' } +$matrixSDKVersionSpec = { :git => 'https://dl-gitlab.example.com/bwmessenger/bundesmessenger/bundesmessenger-ios-sdk', :tag => 'v2.10.0' } # Method to import the MatrixSDK def import_MatrixSDK From 2b3629378a9746119e8ab70d8d8256a0869e9ad4 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Tue, 26 Sep 2023 15:53:26 +0200 Subject: [PATCH 76/82] MESSENGER-5159 update changelog --- CHANGES_BWI.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGES_BWI.md b/CHANGES_BWI.md index 80278a54c..6dafe3ae9 100644 --- a/CHANGES_BWI.md +++ b/CHANGES_BWI.md @@ -1,3 +1,37 @@ +Changes in BWI project 2.10.0 (2023-09-26) +=================================================== + +Upstream merge ✨: +- v1.11.1 + +Features ✨: +- New WYSIWYG composer (#4941) + +Improvements 🙌: +- UI optimization room overview (#4704, #4806) +- Change date format for poll details (#4798) +- Add additional information to the developer menu for Beta app (#4565) +- Update huddle permissions for new rooms (#4928) +- Update element call permissions when resetting room permissions (#5035) +- Disable all WYSIWYG commands (#4955) +- Change date format for maintenance to ISO-8601 (#5071) +- Update notes room layout in room overview (#4802) +- Enable/disable notes room in settings (#4730) + +Bugfix 🐛: +- Crashfix for dm creation when WYSIWYG composer is enabled (#5133) +- Change permalink URL scheme (#4910) +- No popup for successful password change (#4951) + +Translations 🗣 : +- German translations voice message (#5017) +- German translations wrong password (#4857) +- Add special characters for password creation/resetting (#4952) + +SDK API changes ⚠️: + +Build 🧱: + Changes in BWI project 2.9.1 (2023-09-08) =================================================== From 38ae09adf9ae52fdafe4aa6cf31060c47f884a2e Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Wed, 27 Sep 2023 16:33:41 +0200 Subject: [PATCH 77/82] MESSENGER-5159 activate new room overview UI --- Config/BWIBuildSettings.swift | 2 +- Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 01ab6e203..7a0669446 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -680,7 +680,7 @@ class BWIBuildSettings: NSObject { var showPasswordChangedConfirmation = true // MARK: Create Room Menu - var enableAllChatsToolbar = true + var enableAllChatsToolbar = false // MARK: App Config var avoidServerSelectionOnAppConfig = false diff --git a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift index e5d0e31cf..bb6112246 100644 --- a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift +++ b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift @@ -37,7 +37,6 @@ extension BWIBuildSettings { enableNewSessionManagerByDefault = true showMaintenanceInfoMessageType = true ignoreBlockingMaintenance = true - enableAllChatsToolbar = false bwiUseWellKnownPrivacyPolicyLink = true itunesAppLink = "itms://itunes.apple.com/app/bundesmessenger-beta/id1617068656?mt=8" From d3b2d59c1cd9f962f2430d5638ace5e5fc276536 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Thu, 28 Sep 2023 12:21:46 +0200 Subject: [PATCH 78/82] MESSENGER-5160 disable quote function temporary --- Riot/Modules/Room/RoomViewController.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 9e33cee1d..a714b0a4e 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -4108,8 +4108,10 @@ static CGSize kThreadListBarButtonItemImageSize; [self cancelEventSelection]; }]]; } - - if (!isJitsiCallEvent && !selectedEvent.isTimelinePollEvent && + + // bwi: #5160 disable quote function for new wysiwyg composer because it does not work + if (!([RoomViewController mainToolbarClass] == WysiwygInputToolbarView.class) && + !isJitsiCallEvent && !selectedEvent.isTimelinePollEvent && selectedEvent.eventType != MXEventTypeBeaconInfo) { [self.eventMenuBuilder addItemWithType:EventMenuItemTypeQuote From ccaa43d9297413c6f15031ebb2c86b22848266b2 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Thu, 28 Sep 2023 14:07:59 +0200 Subject: [PATCH 79/82] MESSENGER-5160 change color for new wysiwyg composer menu --- .../CreateActionList/View/ComposerCreateActionList.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift index 706c2f1d9..d2852cd77 100644 --- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift +++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift @@ -39,7 +39,7 @@ struct ComposerCreateActionList: View { HStack(spacing: 16) { Image(action.icon) .renderingMode(.template) - .foregroundColor(theme.colors.accent) + .foregroundColor(Color(ThemeService.shared().theme.tintColor)) // bwi: #5160 color changes Text(action.title) .foregroundColor(theme.colors.primaryContent) .font(theme.fonts.body) @@ -58,7 +58,7 @@ struct ComposerCreateActionList: View { HStack(spacing: 16) { Image(textFormattingIcon) .renderingMode(.template) - .foregroundColor(theme.colors.accent) + .foregroundColor(Color(ThemeService.shared().theme.tintColor)) // bwi: #5160 color changes Text(VectorL10n.wysiwygComposerStartActionTextFormatting) .foregroundColor(theme.colors.primaryContent) .font(theme.fonts.body) @@ -66,7 +66,7 @@ struct ComposerCreateActionList: View { Spacer() Toggle("", isOn: $viewModel.textFormattingEnabled) .labelsHidden() - .toggleStyle(SwitchToggleStyle(tint: theme.colors.accent)) + .toggleStyle(SwitchToggleStyle(tint: Color(ThemeService.shared().theme.tintColor))) // bwi: #5160 color changes .onChange(of: viewModel.textFormattingEnabled) { isOn in viewModel.send(viewAction: .toggleTextFormatting(isOn)) } From 14abd996ed135fe7a8a461e5b74c75507350e934 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Fri, 29 Sep 2023 12:33:10 +0200 Subject: [PATCH 80/82] MESSENGER-5159 update chat overview on hiding notes room --- .../Modules/ContextMenu/ActionProviders/RoomActionProvider.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift b/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift index 2b03d0bb6..2ef2da675 100644 --- a/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift +++ b/Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift @@ -117,6 +117,7 @@ class RoomActionProvider: RoomActionProviderProtocol { title: BWIL10n.homeContextMenuPersonalNotes, image: UIImage(systemName: "star.slash")) { _ in PersonalNotesSettings().personalNotesVisible = false + NotificationCenter.default.post(name: AllChatsLayoutSettingsManager.didUpdateSettings, object: self) } } From e51542ab503e4711f74ef368b9f5325e811365a0 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Fri, 29 Sep 2023 13:42:16 +0200 Subject: [PATCH 81/82] MESSENGER-5159 add releasenotes --- Riot/Assets/de.lproj/Bwi.strings | 2 +- Riot/Assets/en.lproj/Bwi.strings | 2 +- Riot/Assets/new_features.html | 28 +++++++++++++++++ Riot/Generated/BWIStrings.swift | 52 ++++++++++++++++++++++++++++---- Riot/Generated/Strings.swift | 2 +- 5 files changed, 77 insertions(+), 9 deletions(-) diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 818ff0ad0..9ed3d7de0 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -535,7 +535,7 @@ "bwi_settings_new_features_show_features" = "Neue Funktionen anzeigen"; "bwi_feature_banner_header" = "Neue Funktionen"; "bwi_feature_banner_show_more_button" = "Erfahre mehr"; -"bwi_feature_banner_advertisement_text" = "Mit dem neuen Feature Ruhezeiteneinstellung lassen sich Benachrichtigungen nach Wochentag und Uhrzeit ein- bzw stummschalten."; +"bwi_feature_banner_advertisement_text" = "Der neue Nachrichteneditor: Beim Schreiben von Nachrichten kannst Du jetzt sehr einfach Textformatierungen verwenden. Die Darstellung der Raumübersicht wurde überarbeitet und verbessert."; // MARK: - Onboarding "onboarding_splash_login_button_title" = "Loslegen"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index e45e03506..f45790dba 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -444,7 +444,7 @@ "bwi_settings_new_features_show_features" = "Show new features"; "bwi_feature_banner_header" = "New Features"; "bwi_feature_banner_show_more_button" = "Learn more"; -"bwi_feature_banner_advertisement_text" = "A new feature for notifications times allows you to define time intervals for filtering incoming push notifications."; +"bwi_feature_banner_advertisement_text" = "The new message editor: You can now easily use text formatting when writing messages. The user interface of the room overview has been improved."; // MARK: - Onboarding "onboarding_splash_login_button_title" = "Let's go"; diff --git a/Riot/Assets/new_features.html b/Riot/Assets/new_features.html index a379e5e7d..91e227f30 100644 --- a/Riot/Assets/new_features.html +++ b/Riot/Assets/new_features.html @@ -26,6 +26,34 @@ +

    Version 2.9.1 diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index 2f62438b4..6259000bb 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -239,7 +239,7 @@ public class BWIL10n: NSObject { public static var bwiErrorRoomNotAvailableTitle: String { return BWIL10n.tr("Bwi", "bwi_error_room_not_available_title") } - /// Mit dem neuen Feature Ruhezeiteneinstellung lassen sich Benachrichtigungen nach Wochentag und Uhrzeit ein- bzw stummschalten. + /// Der neue Nachrichteneditor: Beim Schreiben von Nachrichten kannst Du jetzt sehr einfach Textformatierungen verwenden. Die Darstellung der Raumübersicht wurde überarbeitet und verbessert. public static var bwiFeatureBannerAdvertisementText: String { return BWIL10n.tr("Bwi", "bwi_feature_banner_advertisement_text") } @@ -535,10 +535,38 @@ public class BWIL10n: NSObject { public static var bwiSettingsDeveloperApplyOrigAppConfig: String { return BWIL10n.tr("Bwi", "bwi_settings_developer_apply_orig_app_config") } + /// Capabilities + public static var bwiSettingsDeveloperCapabilities: String { + return BWIL10n.tr("Bwi", "bwi_settings_developer_capabilities") + } /// Neuen Notizen-Raum Raum erstellen public static var bwiSettingsDeveloperCreateNewPersonalNotesRoom: String { return BWIL10n.tr("Bwi", "bwi_settings_developer_create_new_personal_notes_room") } + /// Key Backup + public static var bwiSettingsDeveloperKeyBackup: String { + return BWIL10n.tr("Bwi", "bwi_settings_developer_key_backup") + } + /// Algorithmus + public static var bwiSettingsDeveloperKeyBackupAlgorithm: String { + return BWIL10n.tr("Bwi", "bwi_settings_developer_key_backup_algorithm") + } + /// Gespeicherte Schlüssel + public static var bwiSettingsDeveloperKeyBackupCount: String { + return BWIL10n.tr("Bwi", "bwi_settings_developer_key_backup_count") + } + /// ETag + public static var bwiSettingsDeveloperKeyBackupEtag: String { + return BWIL10n.tr("Bwi", "bwi_settings_developer_key_backup_etag") + } + /// Version + public static var bwiSettingsDeveloperKeyBackupVersion: String { + return BWIL10n.tr("Bwi", "bwi_settings_developer_key_backup_version") + } + /// Maintenance + public static var bwiSettingsDeveloperMaintenance: String { + return BWIL10n.tr("Bwi", "bwi_settings_developer_maintenance") + } /// Der alte Notizen-Raum Raum wurde durch einen neuen ersetzt. public static var bwiSettingsDeveloperNewPersonalNotesRoomCreated: String { return BWIL10n.tr("Bwi", "bwi_settings_developer_new_personal_notes_room_created") @@ -579,6 +607,18 @@ public class BWIL10n: NSObject { public static var bwiSettingsDeveloperUnrestrictUser: String { return BWIL10n.tr("Bwi", "bwi_settings_developer_unrestrict_user") } + /// Account Data + public static var bwiSettingsDeveloperUserAccountData: String { + return BWIL10n.tr("Bwi", "bwi_settings_developer_user_account_data") + } + /// Type + public static var bwiSettingsDeveloperUserAccountDataType: String { + return BWIL10n.tr("Bwi", "bwi_settings_developer_user_account_data_type") + } + /// Well-Known + public static var bwiSettingsDeveloperWellKnown: String { + return BWIL10n.tr("Bwi", "bwi_settings_developer_well_known") + } /// Einen Raum für private Notizen anzeigen public static var bwiSettingsEnableNotesRoomSummary: String { return BWIL10n.tr("Bwi", "bwi_settings_enable_notes_room_summary") @@ -1607,6 +1647,10 @@ public class BWIL10n: NSObject { public static var settingsOther: String { return BWIL10n.tr("Bwi", "settings_other") } + /// Dein Passwort wurde erfolgreich geändert. + public static var settingsPasswordChanged: String { + return BWIL10n.tr("Bwi", "settings_password_changed") + } /// Regeln: Mindestlänge 8 Zeichen, mind. 1 Sonderzeichen, 1 Groß- und 1 Kleinbuchstaben und 1 Ziffer public static var settingsPasswordCondition: String { return BWIL10n.tr("Bwi", "settings_password_condition") @@ -1631,10 +1675,6 @@ public class BWIL10n: NSObject { public static var settingsPasswordTooShortMessage: String { return BWIL10n.tr("Bwi", "settings_password_too_short_message") } - /// Dein Passwort wurde aktualisiert - public static var settingsPasswordUpdated: String { - return BWIL10n.tr("Bwi", "settings_password_updated") - } /// Permalink Prefix public static var settingsPermalinkPrefixPickerTitle: String { return BWIL10n.tr("Bwi", "settings_permalink_prefix_picker_title") @@ -1775,7 +1815,7 @@ public class BWIL10n: NSObject { public static func voiceMessageRemainingRecordingTime(_ p1: String) -> String { return BWIL10n.tr("Bwi", "voice_message_remaining_recording_time", p1) } - /// Sliden zum Abbrechen + /// Wischen zum Abbrechen public static var voiceMessageSlideToCancel: String { return BWIL10n.tr("Bwi", "voice_message_slide_to_cancel") } diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index ce6d62fd6..b446b0823 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -215,7 +215,7 @@ public class VectorL10n: NSObject { public static var allChatsSectionTitle: String { return VectorL10n.tr("Vector", "all_chats_section_title") } - /// All chats + /// Chats public static var allChatsTitle: String { return VectorL10n.tr("Vector", "all_chats_title") } From 0777e7e6abe75a02c78c705f24e62e9d549b87a1 Mon Sep 17 00:00:00 2001 From: JanNiklas Grabowski Date: Wed, 4 Oct 2023 11:46:02 +0200 Subject: [PATCH 82/82] MESSENGER-5159 text changes --- Riot/Assets/de.lproj/Bwi.strings | 2 +- Riot/Assets/en.lproj/Bwi.strings | 2 +- Riot/Generated/BWIStrings.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 9ed3d7de0..42fa341ff 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -535,7 +535,7 @@ "bwi_settings_new_features_show_features" = "Neue Funktionen anzeigen"; "bwi_feature_banner_header" = "Neue Funktionen"; "bwi_feature_banner_show_more_button" = "Erfahre mehr"; -"bwi_feature_banner_advertisement_text" = "Der neue Nachrichteneditor: Beim Schreiben von Nachrichten kannst Du jetzt sehr einfach Textformatierungen verwenden. Die Darstellung der Raumübersicht wurde überarbeitet und verbessert."; +"bwi_feature_banner_advertisement_text" = "Beim Schreiben von Nachrichten kannst Du jetzt sehr einfach Textformatierungen verwenden."; // MARK: - Onboarding "onboarding_splash_login_button_title" = "Loslegen"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index f45790dba..6267f0522 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -444,7 +444,7 @@ "bwi_settings_new_features_show_features" = "Show new features"; "bwi_feature_banner_header" = "New Features"; "bwi_feature_banner_show_more_button" = "Learn more"; -"bwi_feature_banner_advertisement_text" = "The new message editor: You can now easily use text formatting when writing messages. The user interface of the room overview has been improved."; +"bwi_feature_banner_advertisement_text" = "You can now easily use text formatting when writing messages."; // MARK: - Onboarding "onboarding_splash_login_button_title" = "Let's go"; diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index 6259000bb..fc0327740 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -239,7 +239,7 @@ public class BWIL10n: NSObject { public static var bwiErrorRoomNotAvailableTitle: String { return BWIL10n.tr("Bwi", "bwi_error_room_not_available_title") } - /// Der neue Nachrichteneditor: Beim Schreiben von Nachrichten kannst Du jetzt sehr einfach Textformatierungen verwenden. Die Darstellung der Raumübersicht wurde überarbeitet und verbessert. + /// Beim Schreiben von Nachrichten kannst Du jetzt sehr einfach Textformatierungen verwenden. public static var bwiFeatureBannerAdvertisementText: String { return BWIL10n.tr("Bwi", "bwi_feature_banner_advertisement_text") }