From dc6a1644d3961b6ba2a9878bda2c06eb374a50dc Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Thu, 13 Oct 2022 13:28:25 +0300 Subject: [PATCH 1/6] Merge branch 'hotfix/1.9.8' into master --- Riot/Categories/UIViewController+RiotSearch.m | 11 ++++++++--- .../Common/SwiftUI/VectorHostingController.swift | 10 +++++++--- changelog.d/6762.bugfix | 1 + changelog.d/6833.bugfix | 1 + 4 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 changelog.d/6762.bugfix create mode 100644 changelog.d/6833.bugfix diff --git a/Riot/Categories/UIViewController+RiotSearch.m b/Riot/Categories/UIViewController+RiotSearch.m index 76d3af972..98556960f 100644 --- a/Riot/Categories/UIViewController+RiotSearch.m +++ b/Riot/Categories/UIViewController+RiotSearch.m @@ -89,8 +89,12 @@ self.navigationItem.leftBarButtonItem = nil; // Add the search bar - self.navigationItem.titleView = self.searchBar; - + UIView *searchBarContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 44)]; + searchBarContainer.backgroundColor = [UIColor clearColor]; + searchBarContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth; + + self.navigationItem.titleView = searchBarContainer; + [searchBarContainer addSubview:self.searchBar]; self.extendedLayoutIncludesOpaqueBars = YES; // On iPad, there is no cancel button inside the UISearchBar @@ -177,8 +181,9 @@ // Initialise internal data at the first call searchInternals = [[UIViewControllerRiotSearchInternals alloc] init]; - UISearchBar *searchBar = [[UISearchBar alloc] init]; + UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 44)]; searchBar.showsCancelButton = YES; + searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; searchBar.delegate = (id)self; searchInternals.searchBar = searchBar; diff --git a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift index 98faba5c0..493c29560 100644 --- a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift +++ b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift @@ -88,9 +88,13 @@ class VectorHostingController: UIHostingController { override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() - if let navigationController = navigationController, navigationController.isNavigationBarHidden != isNavigationBarHidden { - navigationController.isNavigationBarHidden = isNavigationBarHidden - } + guard + let navigationController = navigationController, + navigationController.topViewController == self, + navigationController.isNavigationBarHidden != isNavigationBarHidden + else { return } + + navigationController.isNavigationBarHidden = isNavigationBarHidden } override func viewDidLayoutSubviews() { diff --git a/changelog.d/6762.bugfix b/changelog.d/6762.bugfix new file mode 100644 index 000000000..78e6b08b9 --- /dev/null +++ b/changelog.d/6762.bugfix @@ -0,0 +1 @@ +Element freezes after searching in a room. \ No newline at end of file diff --git a/changelog.d/6833.bugfix b/changelog.d/6833.bugfix new file mode 100644 index 000000000..4ea819b83 --- /dev/null +++ b/changelog.d/6833.bugfix @@ -0,0 +1 @@ +All Chats: Fix a header glitch when aborting a pop gesture. From 2a4fbfd2b30a340cffa46063e16e684f9f721b93 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Thu, 13 Oct 2022 13:36:46 +0300 Subject: [PATCH 2/6] finish version++ --- changelog.d/6762.bugfix | 1 - changelog.d/6833.bugfix | 1 - 2 files changed, 2 deletions(-) delete mode 100644 changelog.d/6762.bugfix delete mode 100644 changelog.d/6833.bugfix diff --git a/changelog.d/6762.bugfix b/changelog.d/6762.bugfix deleted file mode 100644 index 78e6b08b9..000000000 --- a/changelog.d/6762.bugfix +++ /dev/null @@ -1 +0,0 @@ -Element freezes after searching in a room. \ No newline at end of file diff --git a/changelog.d/6833.bugfix b/changelog.d/6833.bugfix deleted file mode 100644 index 4ea819b83..000000000 --- a/changelog.d/6833.bugfix +++ /dev/null @@ -1 +0,0 @@ -All Chats: Fix a header glitch when aborting a pop gesture. From 2fc0832ca8dcbf945b3fab1bec901c6837e55ab5 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Tue, 18 Oct 2022 16:09:06 +0300 Subject: [PATCH 3/6] Release 1.9.9 (#6924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Login with QR UI components (#6790) * Display QR button on login screen if HS supports * Create start screen * Add build flag * Connect start screen to the login * QR display screen * Move `LabelledDividerView` into separate file * Show display QR screen on button tap * Add swift concurreny to CameraAccessManager * Introduce `QRLoginServiceProtocol` * Use new service in screens * Introduce scan QR code screen * Remove hardcoded service availability * Remove unnecessary import * Add confirmation screen * Add loading screen * Fix ZXingObjc targets * Add failure screen * Add strings * Various UI tweaks, navigation according to the service state * Fix tests * Add string for invalid QR error * Add QR login service mode * dark mode support almost completed * 'View all' button in other sessions list * Changelog * Only use device type name as fallback for session display name * Add changelog * Revert translation changes * Fix copyright year * updated the package and added a circle background to the + button * QR login from device manager (#6818) * Add link device button into the sessions overview screen * Run Swift format * Fix tests * Fix a crash in tests * Fix PR remark * Bring back English string for now to unbreak the build * pr comment addressed * Fix composer resizing animations * Remove prefix from tests * Add back generated user session name string * Fixed swiftformat warnings * Remove current app name from device type name * updated package version * QR Login additional flags (#6825) * Include app name in default session display name * Add changelog * edit is displayed in the WYSIWYG, but the send button still sends a new message instead of correcting the edit * Move composer send media selection to it's own coordinator and us e ioS 15 api for sheet * fix newline in pod lockfile * fix newline * Add labs flag and strings * Translated using Weblate (Italian) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Estonian) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (German) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Icelandic) Currently translated at 86.0% (1919 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/is/ * Split out models, add some tests and fix some formatting. * Fixed formatting * Expose AuthenticationRestClient async login token generation method (MSC3882) * Only update the nav bar from the top SwiftUI view. * Bugfix: Element freezes after searching in a room #6762 * Tidy-up formatting. * Hide Maximise button as animations not working correctly * Remove the ffmpeg pod. * created the replace formatted text function * Fix authentication tests. * Translated using Weblate (Swedish) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/sv/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translations update from Weblate (#6843) * Translated using Weblate (Italian) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Estonian) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (German) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Icelandic) Currently translated at 86.0% (1919 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/is/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2229 of 2229 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ Co-authored-by: random Co-authored-by: Jozef Gaal Co-authored-by: Linerly Co-authored-by: Priit Jõerüüt Co-authored-by: Ihor Hordiichuk Co-authored-by: Vri Co-authored-by: Thomas Schmidt Co-authored-by: Sveinn í Felli Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Weblate * Translations update from Weblate (#6844) * Translated using Weblate (Icelandic) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/is/ * Translated using Weblate (Swedish) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/sv/ Co-authored-by: Sveinn í Felli Co-authored-by: LinAGKar Co-authored-by: Weblate * edit works functionally (no UI changes yet) * package update * formatted reply support * error logging improvement * code improvements * improving code * improving overall code * Filter button * Verified sessions * reverting the xcscheme file * revert * todo reminder * Ignore OS version from web based sessions (PSG-826) (#6852) * Ignore OS version from web based sessions * Add changelog * Clear filter button * Device Manager: Rename Session (#6826) * Publish the user sessions overview data. * Add UserSessionName screen. * Update logout action to match Figma more closely. * UI and unit tests * Device Manager: Verify session (#6832) * Initial implementation * Add verificationState to UserSessionInfo * Listen for changes device changes in the service. * Remove loader for static location sharing * Remove loader for live location sharing * Move completions around * Add changelog.d file * edit mode changes the send button * right assets * color improvements and implementing the cancel callback * implemented cancel, but I need to actually need to solve a problem where the previous text is not stored in case of cancel of the reply/edit * edit reply completed, just need to polish the UI a bit more. * Renamed OtherUserSessionsFilter to UserOtherSessionsFilter * Changelog * Formating * some UI tweaks * Add voice broadcast state event (#6785) * Tidy up TabBarCoordinator now that AllChatsCoordinator exists. * Update Riot/Assets/en.lproj/Vector.strings Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * Update RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsModels.swift Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * Single scale image * Renamed filerWasChanged to filterWasChanged * Revert collections update * Added accessibility label to filter menu * pr comment + some design improvements * should now be aligned to the designs * get rid of additional UIHostingController. Cleanup and comments. * legacy sendMode code separation to have RiotSwiftUI be able to build. * Added last activity item to session details screen * fix * More comments * Update strings for unknown sessions. * Fix RiotTests. * code improvements * wiftlint * Remove verify session button for other sessions when the current session is unverified * Implement login with scanned QR code flows * Fix initiator flows and e2e tests * Add missing rendezvousServerBaseURL buildSetting * Add changelog * Remove extra step as no longer required * Fix typo * Remove now unnecessary creator public key publishing. Rebuilt flows and fixed tests * Fix UI tests for features currently disabled * composer code improvement * improving code * unit tests * Tests completed! The only doubt I have left is about testing the string content considering the localisations in tests * code improvement * code improvement * ui tests should now work on CI * ui test should work now * ui tests fix on ci * ui test improvements * Correct verificationStatusAdditionalInfoText for other session * Changelog * Correct label for other verified session * Formating fixes * Update enhancement issue template Link to discussions for cross-platform and feature requests * Updates based on comments. * more comment updates * unit tests improved! * improved UI tests * Add a SignOutFlowPresenter. (#6854) Used in AllChats, UserSessions and Settings. (TabBarCoordinator is unnecessary as signout will be removed from there). * Additional translations and accessiblity labels * Added E2EE support on sessions created through QR code login * Fixed various SwiftFormat warnings * Remove unnecessary private key requests as it already happens automatically when marking the existing device as verified * Allow the login with qr button to be shown on the login screen if the currently selected homeserver supports it * Check login intents match between QR code and current flow * Fix authentication unit tests * Login against the homeserver provided through the QR code * Navigating to session overview goes to session details * Changelog * Fix typo * custom tint + blinking dark theme text color issue fix * Identify inactive sessions * Changelog * Translated using Weblate (German) Currently translated at 100.0% (2269 of 2269 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (French) Currently translated at 100.0% (2269 of 2269 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/fr/ * Translated using Weblate (Italian) Currently translated at 100.0% (2269 of 2269 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2269 of 2269 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2269 of 2269 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2269 of 2269 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2269 of 2269 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * opacity transition implemented * 0.15 animation * updated UI tests * project yml with the latest working version of the package * "Notifications on this device" not refreshed in user settings screen * Update tools. • Un-pin versions to update tools on each release. • Switch to SwiftFormat as a pod. * All chats shows no rooms in the list - Fixed * added the grabber and the corner radius * the right corner radius value * ios 16 custom detent * height value * Revert "height value" This reverts commit fae63c6f768d2dd375d431bc16acca31e2dd56bb. * Revert "ios 16 custom detent" This reverts commit 758dacc00e77c57b787aae71405a1f90c03c3666. * better padding * Update Wysiwyg dep and use new focused param on WysiwygComposerView * Filter out application section if needed * Add changelog.d file * design improvements! * designs are perfect now * Fix bubble cell visibility (#6893) * updated the swiftpm * package resolved * Set inputAccessoryViewForKeyboard so that interactive dismissal works. * [Voice Broadcast] Aggregate the chunks of voice messages in room timeline (#6891) * Add missing changelogs * changelog * changelog * changelog linked to an issue * Add weak self reference to closure * spacing improved further * Use unstable prefixes for QR code login * Changelog * Force update client information * Changelog * Fix incorrect Task creation for processing scanned qr codes * Check and mark the received MSK as trusted before locally verifying the existing device * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate * Translations update from Weblate (#6908) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/bg/ * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal * Translations update from Weblate (#6909) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (49 of 49 strings) Translation: Element iOS/Element iOS (Push) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-push/bg/ * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate * Translations update from Weblate (#6908) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/bg/ * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal * Translations update from Weblate (#6910) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translations update from Weblate (#6908) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/bg/ * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal * Translations update from Weblate (#6909) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (49 of 49 strings) Translation: Element iOS/Element iOS (Push) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-push/bg/ * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate * Translations update from Weblate (#6908) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/bg/ * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate * Translations update from Weblate (#6911) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/bg/ * Translations update from Weblate (#6909) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (49 of 49 strings) Translation: Element iOS/Element iOS (Push) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-push/bg/ * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate * Translations update from Weblate (#6908) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/bg/ * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal * Translations update from Weblate (#6910) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translations update from Weblate (#6908) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/bg/ * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal * Translations update from Weblate (#6909) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (49 of 49 strings) Translation: Element iOS/Element iOS (Push) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-push/bg/ * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate * Translations update from Weblate (#6908) * Translated using Weblate (Bulgarian) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/bg/ * Translations update from Weblate (#6907) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate Co-authored-by: Slavi Pantaleev Co-authored-by: Weblate Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal * Translations update from Weblate (#6915) * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Russian) Currently translated at 81.2% (1870 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Bulgarian) Currently translated at 66.4% (1529 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/bg/ * Translated using Weblate (Italian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 97.5% (2245 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2301 of 2301 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2302 of 2302 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ Co-authored-by: Vri Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: random Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk Co-authored-by: Priit Jõerüüt Co-authored-by: Linerly Co-authored-by: Jozef Gaal Co-authored-by: Weblate * Missing change from fix * changelog * Only running alpha builds when PR labeled with `Trigger-PR-Build` * Configure codecov flags and have them be carried forward * Add pull request change types for triggering alpha builds * changelog.d: Upgrade MatrixSDK version ([v0.24.1](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.24.1)). * version++ Co-authored-by: Anderas Co-authored-by: Mauro Romito Co-authored-by: Aleksandrs Proskurins Co-authored-by: Johannes Marbach Co-authored-by: aringenbach Co-authored-by: David Langley Co-authored-by: random Co-authored-by: Jozef Gaal Co-authored-by: Linerly Co-authored-by: Priit Jõerüüt Co-authored-by: Ihor Hordiichuk Co-authored-by: Vri Co-authored-by: Thomas Schmidt Co-authored-by: Sveinn í Felli Co-authored-by: Stefan Ceriu Co-authored-by: Aleksandrs Proskurins Co-authored-by: Doug Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> Co-authored-by: Shunmugaraj Co-authored-by: LinAGKar Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Weblate Co-authored-by: Element Translate Bot Co-authored-by: Alfonso Grillo Co-authored-by: Alfonso Grillo Co-authored-by: Yoan Pintas Co-authored-by: Gil Eluard Co-authored-by: Hugh Nimmo-Smith Co-authored-by: Kat Gerasimova Co-authored-by: Velin92 <34335419+Velin92@users.noreply.github.com> Co-authored-by: Thibault Martin Co-authored-by: Johannes Marbach Co-authored-by: Nui Harime Co-authored-by: Szimszon Co-authored-by: Slavi Pantaleev Co-authored-by: gulekismail --- .github/ISSUE_TEMPLATE/enhancement.yml | 2 +- .github/workflows/ci-tests.yml | 2 + .github/workflows/ci-ui-tests.yml | 2 + .github/workflows/release-alpha.yml | 7 +- .github/workflows/triage-priority-bugs.yml | 2 +- CHANGES.md | 57 +++ Config/AppConfiguration.swift | 5 +- Config/AppVersion.xcconfig | 4 +- Config/BuildSettings.swift | 15 + Config/CommonConfiguration.swift | 2 +- Podfile | 10 +- Podfile.lock | 20 +- .../xcshareddata/swiftpm/Package.resolved | 8 + .../Contents.json | 12 + .../Secure connection.svg | 11 + .../exclamation_circle.imageset/Contents.json | 12 + .../exclamation_circle.svg | 3 + .../Composer/Bold.imageset/Bold.png | Bin 0 -> 470 bytes .../Composer/Bold.imageset/Bold@2x.png | Bin 0 -> 737 bytes .../Composer/Bold.imageset/Bold@3x.png | Bin 0 -> 1084 bytes .../Composer/Bold.imageset/Contents.json | 23 + .../Composer/Code.imageset/Code.png | Bin 0 -> 591 bytes .../Composer/Code.imageset/Code@2x.png | Bin 0 -> 894 bytes .../Composer/Code.imageset/Code@3x.png | Bin 0 -> 1089 bytes .../Composer/Code.imageset/Contents.json | 23 + .../Images.xcassets/Composer/Contents.json | 6 + .../Indent_increase.imageset/Contents.json | 23 + .../Indent increase.png | Bin 0 -> 275 bytes .../Indent increase@2x.png | Bin 0 -> 445 bytes .../Indent increase@3x.png | Bin 0 -> 584 bytes .../Composer/Italic.imageset/Contents.json | 23 + .../Composer/Italic.imageset/Italic.png | Bin 0 -> 435 bytes .../Composer/Italic.imageset/Italic@2x.png | Bin 0 -> 660 bytes .../Composer/Italic.imageset/Italic@3x.png | Bin 0 -> 940 bytes .../Composer/Link.imageset/Contents.json | 23 + .../Composer/Link.imageset/Link.png | Bin 0 -> 682 bytes .../Composer/Link.imageset/Link@2x.png | Bin 0 -> 1241 bytes .../Composer/Link.imageset/Link@3x.png | Bin 0 -> 1635 bytes .../Numbered list.imageset/Contents.json | 23 + .../Numbered list.imageset/Numbered list.png | Bin 0 -> 287 bytes .../Numbered list@2x.png | Bin 0 -> 465 bytes .../Numbered list@3x.png | Bin 0 -> 719 bytes .../Composer/Quote.imageset/Contents.json | 23 + .../Composer/Quote.imageset/Quote.png | Bin 0 -> 474 bytes .../Composer/Quote.imageset/Quote@2x.png | Bin 0 -> 848 bytes .../Composer/Quote.imageset/Quote@3x.png | Bin 0 -> 1112 bytes .../Strikethrough.imageset/Contents.json | 23 + .../Strikethrough.imageset/Strikethrough.png | Bin 0 -> 585 bytes .../Strikethrough@2x.png | Bin 0 -> 962 bytes .../Strikethrough@3x.png | Bin 0 -> 1354 bytes .../Underlined.imageset/Contents.json | 23 + .../Underlined.imageset/Underlined.png | Bin 0 -> 410 bytes .../Underlined.imageset/Underlined@2x.png | Bin 0 -> 662 bytes .../Underlined.imageset/Underlined@3x.png | Bin 0 -> 980 bytes .../bullet_list.imageset/Bullet list.png | Bin 0 -> 217 bytes .../bullet_list.imageset/Bullet list@2x.png | Bin 0 -> 347 bytes .../bullet_list.imageset/Bullet list@3x.png | Bin 0 -> 586 bytes .../bullet_list.imageset/Contents.json | 23 + .../indent_decrease.imageset/Contents.json | 23 + .../Indent decrease.png | Bin 0 -> 274 bytes .../Indent decrease@2x.png | Bin 0 -> 449 bytes .../Indent decrease@3x.png | Bin 0 -> 597 bytes .../maximise_composer.imageset/Contents.json | 23 + .../maximise_composer.png | Bin 0 -> 320 bytes .../maximise_composer@2x.png | Bin 0 -> 450 bytes .../maximise_composer@3x.png | Bin 0 -> 553 bytes .../minimise_composer.imageset/Contents.json | 23 + .../minimise_composer.png | Bin 0 -> 348 bytes .../minimise_composer@2x.png | Bin 0 -> 488 bytes .../minimise_composer@3x.png | Bin 0 -> 638 bytes .../Contents.json | 23 + .../start_compose_module.png | Bin 0 -> 292 bytes .../start_compose_module@2x.png | Bin 0 -> 372 bytes .../start_compose_module@3x.png | Bin 0 -> 502 bytes .../Contents.json | 12 + .../user_other_sessions_filter.svg | 3 + .../Contents.json | 12 + .../user_other_sessions_filter_selected.svg | 6 + .../Contents.json | 12 + .../user_other_sessions_inactive.svg | 3 + .../Contents.json | 12 + .../user_other_sessions_unverified.svg | 5 + .../Contents.json | 12 + .../user_other_sessions_verified.svg | 4 + .../Contents.json | 12 + ...ser_session_list_item_inactive_session.svg | 3 + .../Contents.json | 15 + .../user_session_verification_unknown.svg | 4 + .../action_live.imageset/Contents.json | 15 + .../action_live.imageset/action_live.svg | 7 + Riot/Assets/bg.lproj/InfoPlist.strings | 10 +- Riot/Assets/bg.lproj/Localizable.strings | 51 +++ Riot/Assets/bg.lproj/Vector.strings | 21 + Riot/Assets/de.lproj/Vector.strings | 274 ++++++++---- Riot/Assets/en.lproj/Untranslated.strings | 2 + Riot/Assets/en.lproj/Vector.strings | 98 ++++- Riot/Assets/et.lproj/Vector.strings | 53 ++- Riot/Assets/fr.lproj/Vector.strings | 101 ++++- Riot/Assets/hu.lproj/Vector.strings | 124 +++++- Riot/Assets/id.lproj/Vector.strings | 252 ++++++++--- Riot/Assets/is.lproj/InfoPlist.strings | 6 +- Riot/Assets/is.lproj/Vector.strings | 203 ++++++++- Riot/Assets/it.lproj/Vector.strings | 125 +++++- Riot/Assets/pt_BR.lproj/Vector.strings | 124 +++++- Riot/Assets/ru.lproj/Vector.strings | 5 + Riot/Assets/sk.lproj/Vector.strings | 128 +++++- Riot/Assets/sv.lproj/InfoPlist.strings | 6 +- Riot/Assets/uk.lproj/Vector.strings | 126 +++++- Riot/Categories/MXRestClient+Async.swift | 14 + Riot/Categories/String.swift | 5 + Riot/Categories/UIDevice.swift | 2 +- Riot/Generated/Images.swift | 24 ++ Riot/Generated/Strings.swift | 294 ++++++++++++- Riot/Generated/UntranslatedStrings.swift | 8 + Riot/Managers/Settings/RiotSettings.swift | 9 +- Riot/Modules/Application/AppCoordinator.swift | 8 - Riot/Modules/Application/LegacyAppDelegate.m | 28 +- .../AuthenticationCoordinator.swift | 11 +- Riot/Modules/Camera/CameraAccessManager.swift | 16 + .../SwiftUI/VectorHostingController.swift | 15 +- .../Home/AllChats/AllChatsCoordinator.swift | 120 +----- .../AllChats/AllChatsViewController.swift | 2 +- .../Common/KeyVerificationCoordinator.swift | 4 +- ...rificationCoordinatorBridgePresenter.swift | 2 +- .../Common/KeyVerificationFlow.swift | 2 +- ...erificationVerifyByScanningViewModel.swift | 39 +- ...eviceVerificationIncomingCoordinator.swift | 2 +- .../DeviceVerificationIncomingViewModel.swift | 8 +- ...erificationSelfVerifyWaitCoordinator.swift | 2 +- ...icationSelfVerifyWaitCoordinatorType.swift | 2 +- ...yVerificationSelfVerifyWaitViewModel.swift | 8 +- ...ificationSelfVerifyWaitViewModelType.swift | 2 +- .../DeviceVerificationStartViewModel.swift | 6 +- .../MatrixKit/Models/Account/MXKAccount.h | 9 + .../MatrixKit/Models/Account/MXKAccount.m | 153 ++++++- .../MXKRoomInputToolbarView.h | 20 +- .../MXKRoomInputToolbarView.m | 2 +- Riot/Modules/QRCode/QRCodeGenerator.swift | 12 +- .../Rendezvous/MockRendezvousTransport.swift | 61 +++ .../Rendezvous/RendezvousService.swift | 274 ++++++++++++ .../Rendezvous/RendezvousTransport.swift | 191 +++++++++ .../RendezvousTransportProtocol.swift | 46 ++ .../Room/CellData/RoomBubbleCellData.h | 3 +- .../Room/CellData/RoomBubbleCellData.m | 95 ++-- .../Modules/Room/DataSources/RoomDataSource.m | 9 +- .../Room/DataSources/RoomDataSource.swift | 170 ++++++-- Riot/Modules/Room/MXKRoomViewController.m | 56 +-- Riot/Modules/Room/MXKRoomViewController.xib | 10 +- Riot/Modules/Room/RoomCoordinator.swift | 1 + Riot/Modules/Room/RoomViewController.h | 4 + Riot/Modules/Room/RoomViewController.m | 279 +++++++++--- Riot/Modules/Room/RoomViewController.swift | 53 +++ .../RoomTimelineCellIdentifier.h | 10 + .../Bubble/BubbleRoomTimelineCellProvider.m | 25 ++ .../VoiceBroadcastIncomingBubbleCell.swift | 39 ++ ...ncomingWithPaginationTitleBubbleCell.swift | 27 ++ ...tIncomingWithoutSenderInfoBubbleCell.swift | 27 ++ ...utgoingWithPaginationTitleBubbleCell.swift | 27 ++ ...tOutgoingWithoutSenderInfoBubbleCell.swift | 41 ++ .../VoiceBroadcastBubbleCell.swift | 113 +++++ .../VoiceBroadcastPlainCell.swift | 65 +++ ...roadcastWithPaginationTitlePlainCell.swift | 27 ++ ...eBroadcastWithoutSenderInfoPlainCell.swift | 27 ++ .../Plain/PlainRoomTimelineCellProvider.h | 4 + .../Plain/PlainRoomTimelineCellProvider.m | 26 ++ .../DisabledRoomInputToolbarView.m | 2 +- .../Views/InputToolbar/RoomInputToolbarView.h | 12 +- .../Views/InputToolbar/RoomInputToolbarView.m | 47 +- .../InputToolbar/RoomInputToolbarView.xib | 8 +- .../WysiwygInputToolbarView.swift | 204 +++++++++ .../WysiwygInputToolbarView.xib | 21 + ...scoveryThreePidDetailsViewController.swift | 1 + ...SettingsIdentityServerViewController.swift | 1 + .../Language/LanguagePickerViewController.m | 2 + .../CountryPickerViewController.m | 1 + .../Security/SecurityViewController.m | 1 + .../Modules/Settings/SettingsViewController.m | 198 +++------ .../SignOut/SignOutFlowPresenter.swift | 162 +++++++ Riot/Modules/TabBar/MasterTabBarController.m | 59 +-- Riot/Modules/TabBar/TabBarCoordinator.swift | 240 +---------- .../AuthenticatedEndpointRequest.swift | 11 + .../MXSession+VoiceBroadcast.swift | 31 ++ .../VoiceBroadcastAggregator.swift | 143 +++++++ .../VoiceBroadcastBuilder.swift | 29 ++ .../VoiceBroadcastChunk.swift | 50 +++ .../VoiceBroadcastSDK/VoiceBroadcastInfo.h | 43 ++ .../VoiceBroadcastSDK/VoiceBroadcastInfo.m | 79 ++++ .../VoiceBroadcastInfo.swift | 38 ++ .../VoiceBroadcastModels.swift | 34 ++ .../VoiceBroadcastService.swift | 280 ++++++++++++ .../VoiceBroadcastServiceError.swift | 38 ++ .../VoiceBroadcastSettings.swift | 28 ++ .../VoiceBroadcastServiceProvider.swift | 120 ++++++ Riot/SupportingFiles/Riot-Bridging-Header.h | 1 + Riot/Utils/EventFormatter.m | 130 +++--- Riot/target.yml | 1 + .../AuthenticationHomeserverViewData.swift | 6 + .../MatrixSDK/AuthenticationRestClient.swift | 6 + .../MatrixSDK/AuthenticationService.swift | 6 +- .../MatrixSDK/AuthenticationState.swift | 4 + .../Login/AuthenticationLoginModels.swift | 6 + .../Login/AuthenticationLoginViewModel.swift | 2 + .../AuthenticationLoginCoordinator.swift | 33 ++ .../View/AuthenticationLoginScreen.swift | 19 + .../QRLogin/Common/Models/QRLoginCode.swift | 100 +++++ .../Service/MatrixSDK/QRLoginService.swift | 404 ++++++++++++++++++ .../Service/Mock/MockQRLoginService.swift | 87 ++++ .../Service/QRLoginServiceProtocol.swift | 100 +++++ .../Common/Views/LabelledDivider.swift | 63 +++ .../AuthenticationQRLoginConfirmModels.swift | 37 ++ ...uthenticationQRLoginConfirmViewModel.swift | 56 +++ ...ationQRLoginConfirmViewModelProtocol.swift | 22 + ...henticationQRLoginConfirmCoordinator.swift | 102 +++++ ...henticationQRLoginConfirmScreenState.swift | 50 +++ .../AuthenticationQRLoginConfirmUITests.swift | 37 ++ ...ticationQRLoginConfirmViewModelTests.swift | 53 +++ .../AuthenticationQRLoginConfirmScreen.swift | 135 ++++++ .../AuthenticationQRLoginDisplayModels.swift | 36 ++ ...uthenticationQRLoginDisplayViewModel.swift | 64 +++ ...ationQRLoginDisplayViewModelProtocol.swift | 22 + ...henticationQRLoginDisplayCoordinator.swift | 103 +++++ ...henticationQRLoginDisplayScreenState.swift | 50 +++ .../AuthenticationQRLoginDisplayUITests.swift | 32 ++ ...ticationQRLoginDisplayViewModelTests.swift | 41 ++ .../AuthenticationQRLoginDisplayScreen.swift | 148 +++++++ .../AuthenticationQRLoginFailureModels.swift | 38 ++ ...uthenticationQRLoginFailureViewModel.swift | 82 ++++ ...ationQRLoginFailureViewModelProtocol.swift | 22 + ...henticationQRLoginFailureCoordinator.swift | 103 +++++ ...henticationQRLoginFailureScreenState.swift | 61 +++ .../AuthenticationQRLoginFailureUITests.swift | 61 +++ ...ticationQRLoginFailureViewModelTests.swift | 53 +++ .../AuthenticationQRLoginFailureScreen.swift | 124 ++++++ .../AuthenticationQRLoginLoadingModels.swift | 35 ++ ...uthenticationQRLoginLoadingViewModel.swift | 72 ++++ ...ationQRLoginLoadingViewModelProtocol.swift | 22 + ...henticationQRLoginLoadingCoordinator.swift | 101 +++++ ...henticationQRLoginLoadingScreenState.swift | 61 +++ .../AuthenticationQRLoginLoadingUITests.swift | 30 ++ ...ticationQRLoginLoadingViewModelTests.swift | 41 ++ .../AuthenticationQRLoginLoadingScreen.swift | 97 +++++ .../AuthenticationQRLoginScanModels.swift | 54 +++ .../AuthenticationQRLoginScanViewModel.swift | 76 ++++ ...ticationQRLoginScanViewModelProtocol.swift | 22 + ...AuthenticationQRLoginScanCoordinator.swift | 131 ++++++ ...AuthenticationQRLoginScanScreenState.swift | 69 +++ .../UI/AuthenticationQRLoginScanUITests.swift | 77 ++++ ...henticationQRLoginScanViewModelTests.swift | 57 +++ .../AuthenticationQRLoginScanScreen.swift | 214 ++++++++++ .../AuthenticationQRLoginStartModels.swift | 37 ++ .../AuthenticationQRLoginStartViewModel.swift | 49 +++ ...icationQRLoginStartViewModelProtocol.swift | 22 + ...uthenticationQRLoginStartCoordinator.swift | 282 ++++++++++++ ...uthenticationQRLoginStartScreenState.swift | 60 +++ .../AuthenticationQRLoginStartUITests.swift | 49 +++ ...enticationQRLoginStartViewModelTests.swift | 57 +++ .../AuthenticationQRLoginStartScreen.swift | 159 +++++++ .../Modules/Common/Mock/MockAppScreens.swift | 12 +- .../Modules/Common/Util/ListBackground.swift | 58 +++ .../Util/PrimaryActionButtonStyle.swift | 7 +- .../Util/SecondaryActionButtonStyle.swift | 7 +- .../LocationSharingCoordinator.swift | 30 +- ...poserCreateActionListBridgePresenter.swift | 90 ++++ .../ComposerCreateActionListCoordinator.swift | 75 ++++ ...kComposerCreateActionListScreenState.swift | 43 ++ .../ComposerCreateActionListModels.swift | 108 +++++ .../UI/ComposerCreateActionListUITests.swift | 34 ++ .../Unit/ComposerCreateActionListTests.swift | 41 ++ .../View/ComposerCreateActionList.swift | 65 +++ ...serCreateActionListVIewModelProtocol.swift | 22 + .../ComposerCreateActionListViewModel.swift | 40 ++ .../Composer/MockComposerScreenState.swift | 65 +++ .../Room/Composer/Model/ComposerModels.swift | 138 ++++++ .../Composer/Model/ComposerViewState.swift | 46 ++ .../Composer/Test/UI/ComposerUITests.swift | 89 ++++ .../Test/Unit/ComposerViewModelTests.swift | 66 +++ .../Modules/Room/Composer/View/Composer.swift | 206 +++++++++ .../Composer/View/FormattingToolbar.swift | 66 +++ .../ViewModel/ComposerViewModel.swift | 56 +++ .../ViewModel/ComposerViewModelProtocol.swift | 24 ++ .../TimelineVoiceBroadcastCoordinator.swift | 107 +++++ .../TimelineVoiceBroadcastProvider.swift | 52 +++ .../TimelineVoiceBroadcastModels.swift | 53 +++ .../TimelineVoiceBroadcastViewModel.swift | 48 +++ ...elineVoiceBroadcastViewModelProtocol.swift | 24 ++ .../View/TimelineVoiceBroadcastView.swift | 51 +++ .../NotificationSettingsCoordinator.swift | 1 + .../UserSessions/Common/DeviceType.swift | 10 +- ...tiveUserSessionLastActivityFormatter.swift | 32 ++ .../Unit/UserSessionNameFormatterTests.swift | 33 ++ .../UserSessions/Common/UserAgentParser.swift | 8 +- .../UserSessions/Common/UserSessionInfo.swift | 42 +- .../Common/UserSessionNameFormatter.swift | 12 +- .../Common/View/DeviceAvatarView.swift | 30 +- .../Common/View/DeviceAvatarViewData.swift | 17 +- .../Common/View/UserSessionCardView.swift | 90 ++-- .../Common/View/UserSessionCardViewData.swift | 80 +++- .../UserSessionsFlowCoordinator.swift | 296 ++++++++++++- ...ssionsFlowCoordinatorBridgePresenter.swift | 8 +- .../UserOtherSessionsCoordinator.swift | 69 +++ .../MockUserOtherSessionsScreenState.swift | 308 +++++++++++++ .../Test/UI/UserOtherSessionsUITests.swift | 59 +++ .../UserOtherSessionsViewModelTests.swift | 162 +++++++ .../UserOtherSessionsFilter.swift | 40 ++ .../UserOtherSessionsModels.swift | 56 +++ .../UserOtherSessionsViewModel.swift | 122 ++++++ .../UserOtherSessionsViewModelProtocol.swift | 22 + .../View/UserOtherSessions.swift | 117 +++++ .../View/UserOtherSessionsHeaderView.swift | 98 +++++ .../UserSessionDetailsCoordinator.swift | 4 +- .../MockUserSessionDetailsScreenState.swift | 70 +-- .../UserSessionDetailsViewModelTests.swift | 26 +- .../UserSessionDetailsViewModel.swift | 54 ++- .../UserSessionNameCoordinator.swift | 98 +++++ .../MockUserSessionNameScreenState.swift | 51 +++ .../Test/UI/UserSessionNameUITests.swift | 44 ++ .../Unit/UserSessionNameViewModelTests.swift | 51 +++ .../UserSessionNameModels.swift | 64 +++ .../UserSessionNameViewModel.swift | 45 ++ .../UserSessionNameViewModelProtocol.swift | 26 ++ .../View/UserSessionName.swift | 78 ++++ .../UserSessionOverviewCoordinator.swift | 17 +- .../MockUserSessionOverviewScreenState.swift | 14 +- .../UserSessionOverviewService.swift | 108 +++-- .../Mock/MockUserSessionOverviewService.swift | 8 +- .../UserSessionOverviewViewModelTests.swift | 21 +- .../UserSessionOverviewModels.swift | 13 +- .../UserSessionOverviewViewModel.swift | 34 +- .../View/UserSessionOverview.swift | 32 +- ...ll.swift => UserSessionOverviewItem.swift} | 38 +- .../UserSessionsOverviewCoordinator.swift | 40 +- .../MatrixSDK/UserSessionsDataProvider.swift | 16 + .../UserSessionsDataProviderProtocol.swift | 4 + .../UserSessionsOverviewService.swift | 92 ++-- .../MockUserSessionsOverviewService.swift | 72 ++-- .../UserSessionsOverviewServiceProtocol.swift | 19 +- .../Test/UI/UserSessionsOverviewUITests.swift | 35 ++ ...rSessionListItemViewDataFactoryTests.swift | 114 +++++ .../UserSessionsOverviewViewModelTests.swift | 25 +- .../UserSessionsOverviewModels.swift | 21 +- .../UserSessionsOverviewViewModel.swift | 59 ++- .../View/UserSessionListItem.swift | 19 +- .../View/UserSessionListItemViewData.swift | 49 +-- .../UserSessionListItemViewDataFactory.swift | 92 ++++ .../View/UserSessionsListViewAllView.swift | 65 +++ .../View/UserSessionsOverview.swift | 100 ++++- RiotSwiftUI/target.yml | 11 +- RiotSwiftUI/targetUITests.yml | 5 +- .../Mocks/MockAuthenticationRestClient.swift | 10 + RiotTests/RendezvousServiceTests.swift | 63 +++ RiotTests/String+Element.swift | 6 + RiotTests/UserAgentParserTests.swift | 12 +- RiotTests/UserSessionsDataProviderTests.swift | 161 +++++++ .../UserSessionsOverviewServiceTests.swift | 111 +++-- RiotTests/target.yml | 1 + codecov.yml | 11 +- project.yml | 3 + 357 files changed, 15106 insertions(+), 1585 deletions(-) create mode 100644 Riot/Assets/Images.xcassets/Authentication/authentication_qrlogin_confirm_icon.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Authentication/authentication_qrlogin_confirm_icon.imageset/Secure connection.svg create mode 100644 Riot/Assets/Images.xcassets/Common/exclamation_circle.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Common/exclamation_circle.imageset/exclamation_circle.svg create mode 100644 Riot/Assets/Images.xcassets/Composer/Bold.imageset/Bold.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Bold.imageset/Bold@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Bold.imageset/Bold@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Bold.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/Code.imageset/Code.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Code.imageset/Code@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Code.imageset/Code@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Code.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/Indent_increase.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/Indent_increase.imageset/Indent increase.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Indent_increase.imageset/Indent increase@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Indent_increase.imageset/Indent increase@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Italic.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/Italic.imageset/Italic.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Italic.imageset/Italic@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Italic.imageset/Italic@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Link.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/Link.imageset/Link.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Link.imageset/Link@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Link.imageset/Link@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Numbered list.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/Numbered list.imageset/Numbered list.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Numbered list.imageset/Numbered list@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Numbered list.imageset/Numbered list@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Quote.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/Quote.imageset/Quote.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Quote.imageset/Quote@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Quote.imageset/Quote@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Strikethrough.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/Strikethrough.imageset/Strikethrough.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Strikethrough.imageset/Strikethrough@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Strikethrough.imageset/Strikethrough@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Underlined.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/Underlined.imageset/Underlined.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Underlined.imageset/Underlined@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/Underlined.imageset/Underlined@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/bullet_list.imageset/Bullet list.png create mode 100644 Riot/Assets/Images.xcassets/Composer/bullet_list.imageset/Bullet list@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/bullet_list.imageset/Bullet list@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/bullet_list.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/indent_decrease.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/indent_decrease.imageset/Indent decrease.png create mode 100644 Riot/Assets/Images.xcassets/Composer/indent_decrease.imageset/Indent decrease@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/indent_decrease.imageset/Indent decrease@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/maximise_composer.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/maximise_composer.imageset/maximise_composer.png create mode 100644 Riot/Assets/Images.xcassets/Composer/maximise_composer.imageset/maximise_composer@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/maximise_composer.imageset/maximise_composer@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/minimise_composer.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/minimise_composer.imageset/minimise_composer.png create mode 100644 Riot/Assets/Images.xcassets/Composer/minimise_composer.imageset/minimise_composer@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/minimise_composer.imageset/minimise_composer@3x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/start_compose_module.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Composer/start_compose_module.imageset/start_compose_module.png create mode 100644 Riot/Assets/Images.xcassets/Composer/start_compose_module.imageset/start_compose_module@2x.png create mode 100644 Riot/Assets/Images.xcassets/Composer/start_compose_module.imageset/start_compose_module@3x.png create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter.imageset/user_other_sessions_filter.svg create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter_selected.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter_selected.imageset/user_other_sessions_filter_selected.svg create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_inactive.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_inactive.imageset/user_other_sessions_inactive.svg create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_unverified.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_unverified.imageset/user_other_sessions_unverified.svg create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_verified.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_verified.imageset/user_other_sessions_verified.svg create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_session_list_item_inactive_session.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_session_list_item_inactive_session.imageset/user_session_list_item_inactive_session.svg create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_session_verification_unknown.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/DeviceManager/user_session_verification_unknown.imageset/user_session_verification_unknown.svg create mode 100644 Riot/Assets/Images.xcassets/Room/Actions/action_live.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Room/Actions/action_live.imageset/action_live.svg create mode 100644 Riot/Modules/Rendezvous/MockRendezvousTransport.swift create mode 100644 Riot/Modules/Rendezvous/RendezvousService.swift create mode 100644 Riot/Modules/Rendezvous/RendezvousTransport.swift create mode 100644 Riot/Modules/Rendezvous/RendezvousTransportProtocol.swift create mode 100644 Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingBubbleCell.swift create mode 100644 Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithPaginationTitleBubbleCell.swift create mode 100644 Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.swift create mode 100644 Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.swift create mode 100644 Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.swift create mode 100644 Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift create mode 100644 Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift create mode 100644 Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithPaginationTitlePlainCell.swift create mode 100644 Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithoutSenderInfoPlainCell.swift create mode 100644 Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift create mode 100644 Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.xib create mode 100644 Riot/Modules/Settings/SignOut/SignOutFlowPresenter.swift create mode 100644 Riot/Modules/VoiceBroadcast/MXSession+VoiceBroadcast.swift create mode 100644 Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastAggregator.swift create mode 100644 Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastBuilder.swift create mode 100644 Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastChunk.swift create mode 100644 Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.h create mode 100644 Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.m create mode 100644 Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.swift create mode 100644 Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastModels.swift create mode 100644 Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastService.swift create mode 100644 Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastServiceError.swift create mode 100644 Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastSettings.swift create mode 100644 Riot/Modules/VoiceBroadcast/VoiceBroadcastServiceProvider.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Common/Models/QRLoginCode.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Common/Service/MatrixSDK/QRLoginService.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Common/Service/Mock/MockQRLoginService.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Common/Service/QRLoginServiceProtocol.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Common/Views/LabelledDivider.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Confirm/AuthenticationQRLoginConfirmModels.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Confirm/AuthenticationQRLoginConfirmViewModel.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Confirm/AuthenticationQRLoginConfirmViewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Confirm/Coordinator/AuthenticationQRLoginConfirmCoordinator.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Confirm/MockAuthenticationQRLoginConfirmScreenState.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Confirm/Test/UI/AuthenticationQRLoginConfirmUITests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Confirm/Test/Unit/AuthenticationQRLoginConfirmViewModelTests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Confirm/View/AuthenticationQRLoginConfirmScreen.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Display/AuthenticationQRLoginDisplayModels.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Display/AuthenticationQRLoginDisplayViewModel.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Display/AuthenticationQRLoginDisplayViewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Display/Coordinator/AuthenticationQRLoginDisplayCoordinator.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Display/MockAuthenticationQRLoginDisplayScreenState.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Display/Test/UI/AuthenticationQRLoginDisplayUITests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Display/Test/Unit/AuthenticationQRLoginDisplayViewModelTests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Display/View/AuthenticationQRLoginDisplayScreen.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Failure/AuthenticationQRLoginFailureModels.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Failure/AuthenticationQRLoginFailureViewModel.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Failure/AuthenticationQRLoginFailureViewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Failure/Coordinator/AuthenticationQRLoginFailureCoordinator.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Failure/MockAuthenticationQRLoginFailureScreenState.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Failure/Test/UI/AuthenticationQRLoginFailureUITests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Failure/Test/Unit/AuthenticationQRLoginFailureViewModelTests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Failure/View/AuthenticationQRLoginFailureScreen.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Loading/AuthenticationQRLoginLoadingModels.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Loading/AuthenticationQRLoginLoadingViewModel.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Loading/AuthenticationQRLoginLoadingViewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Loading/Coordinator/AuthenticationQRLoginLoadingCoordinator.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Loading/MockAuthenticationQRLoginLoadingScreenState.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Loading/Test/UI/AuthenticationQRLoginLoadingUITests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Loading/Test/Unit/AuthenticationQRLoginLoadingViewModelTests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Loading/View/AuthenticationQRLoginLoadingScreen.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Scan/AuthenticationQRLoginScanModels.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Scan/AuthenticationQRLoginScanViewModel.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Scan/AuthenticationQRLoginScanViewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Scan/Coordinator/AuthenticationQRLoginScanCoordinator.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Scan/MockAuthenticationQRLoginScanScreenState.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Scan/Test/UI/AuthenticationQRLoginScanUITests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Scan/Test/Unit/AuthenticationQRLoginScanViewModelTests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Scan/View/AuthenticationQRLoginScanScreen.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Start/AuthenticationQRLoginStartModels.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Start/AuthenticationQRLoginStartViewModel.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Start/AuthenticationQRLoginStartViewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Start/Coordinator/AuthenticationQRLoginStartCoordinator.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Start/MockAuthenticationQRLoginStartScreenState.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Start/Test/UI/AuthenticationQRLoginStartUITests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Start/Test/Unit/AuthenticationQRLoginStartViewModelTests.swift create mode 100644 RiotSwiftUI/Modules/Authentication/QRLogin/Start/View/AuthenticationQRLoginStartScreen.swift create mode 100644 RiotSwiftUI/Modules/Common/Util/ListBackground.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListBridgePresenter.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListCoordinator.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/CreateActionList/MockComposerCreateActionListScreenState.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/CreateActionList/Model/ComposerCreateActionListModels.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/CreateActionList/Test/UI/ComposerCreateActionListUITests.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/CreateActionList/Test/Unit/ComposerCreateActionListTests.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/CreateActionList/ViewModel/ComposerCreateActionListVIewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/CreateActionList/ViewModel/ComposerCreateActionListViewModel.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/Test/UI/ComposerUITests.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/Test/Unit/ComposerViewModelTests.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/View/Composer.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/View/FormattingToolbar.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift create mode 100644 RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastCoordinator.swift create mode 100644 RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastProvider.swift create mode 100644 RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastModels.swift create mode 100644 RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastViewModel.swift create mode 100644 RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastViewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/View/TimelineVoiceBroadcastView.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/Common/InactiveUserSessionLastActivityFormatter.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/Common/Test/Unit/UserSessionNameFormatterTests.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Coordinator/UserOtherSessionsCoordinator.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserOtherSessions/MockUserOtherSessionsScreenState.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/UI/UserOtherSessionsUITests.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/Unit/UserOtherSessionsViewModelTests.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsFilter.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsModels.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsViewModel.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsViewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessions.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessionsHeaderView.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionName/Coordinator/UserSessionNameCoordinator.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionName/MockUserSessionNameScreenState.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionName/Test/UI/UserSessionNameUITests.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionName/Test/Unit/UserSessionNameViewModelTests.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionName/UserSessionNameModels.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionName/UserSessionNameViewModel.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionName/UserSessionNameViewModelProtocol.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionName/View/UserSessionName.swift rename RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/{UserSessionOverviewDisclosureCell.swift => UserSessionOverviewItem.swift} (55%) create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/Unit/UserSessionListItemViewDataFactoryTests.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItemViewDataFactory.swift create mode 100644 RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionsListViewAllView.swift create mode 100644 RiotTests/RendezvousServiceTests.swift create mode 100644 RiotTests/UserSessionsDataProviderTests.swift diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml index 5d9cfb3c8..e776173fa 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -5,7 +5,7 @@ body: - type: markdown attributes: value: | - Thank you for taking the time to propose a new feature or make a suggestion. + Thank you for taking the time to propose an enhancement to an existing feature. If you would like to propose a new feature or a major cross-platform change, please [start a discussion here](https://github.com/vector-im/element-meta/discussions/new?category=ideas) - type: textarea id: usecase attributes: diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 73f195af6..d5a9d105d 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -63,3 +63,5 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 + with: + flags: unittests diff --git a/.github/workflows/ci-ui-tests.yml b/.github/workflows/ci-ui-tests.yml index a5ba76e7b..37a103035 100644 --- a/.github/workflows/ci-ui-tests.yml +++ b/.github/workflows/ci-ui-tests.yml @@ -61,4 +61,6 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 + with: + flags: uitests \ No newline at end of file diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index f9c45d4f0..f2fe6125c 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -4,6 +4,7 @@ on: # Triggers the workflow on any pull request pull_request: + types: [ labeled, synchronized, opened, reopened ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -27,7 +28,11 @@ jobs: build: # Run job if secrets are available (not available for forks). needs: [check-secret] - if: needs.check-secret.outputs.out-key == 'true' + if: | + needs.check-secret.outputs.out-key == 'true' && + (github.event_name == 'push' || + (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'Trigger-PR-Build'))) + name: Release runs-on: macos-12 diff --git a/.github/workflows/triage-priority-bugs.yml b/.github/workflows/triage-priority-bugs.yml index 9e1eee8ba..548cd6aaa 100644 --- a/.github/workflows/triage-priority-bugs.yml +++ b/.github/workflows/triage-priority-bugs.yml @@ -25,7 +25,7 @@ jobs: - uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488 with: project: iOS App Team - column: P1 + column: "Important Issues & Topics (P1)" repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }} P1_issues_to_crypto_team_workboard: diff --git a/CHANGES.md b/CHANGES.md index f7dc561ab..5aa8502c9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,60 @@ +## Changes in 1.9.9 (2022-10-18) + +✨ Features + +- Added RendezvousService and secure channel establishment implementation ([#6806](https://github.com/vector-im/element-ios/pull/6806)) +- Implemented login with QR code flows when scanning from mobile ([#6857](https://github.com/vector-im/element-ios/pull/6857)) + +🙌 Improvements + +- User agents: Ignore OS version for web based sessions (PSG-826). ([#6852](https://github.com/vector-im/element-ios/pull/6852)) +- Upgrade MatrixSDK version ([v0.24.1](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.24.1)). +- Display push toggle for sessions with m.local_notification_settings. events in account_data ([#6797](https://github.com/vector-im/element-ios/issues/6797)) +- CryptoV2: Incoming verification requests ([#6809](https://github.com/vector-im/element-ios/issues/6809)) +- Check enabled field in notification settings push toggles ([#6814](https://github.com/vector-im/element-ios/issues/6814)) +- Only use device type name as fallback for session display name ([#6820](https://github.com/vector-im/element-ios/issues/6820)) +- Include app name in default session display name ([#6828](https://github.com/vector-im/element-ios/issues/6828)) +- Tidy up TabBarCoordinator now that AllChatsCoordinator exists. ([#6853](https://github.com/vector-im/element-ios/issues/6853)) +- Sign Out: Add a SignOutFlowPresenter and use this in All Chats, Settings and the Device Manager. ([#6854](https://github.com/vector-im/element-ios/issues/6854)) +- Improved the Rich Text Editor to match design requirements. ([#6903](https://github.com/vector-im/element-ios/issues/6903)) + +🐛 Bugfixes + +- Filter out application section in session details if needed. ([#6898](https://github.com/vector-im/element-ios/pull/6898)) +- Rich text editor now supports interactive dismissal by dragging the timeline. ([#6919](https://github.com/vector-im/element-ios/pull/6919), [#6900](https://github.com/vector-im/element-ios/issues/6900)) +- Location sharing: removing the loader. ([#5571](https://github.com/vector-im/element-ios/issues/5571)) +- Element freezes after searching in a room. ([#6762](https://github.com/vector-im/element-ios/issues/6762)) +- Settings: Use regular titles for all of the sub-screens. ([#6804](https://github.com/vector-im/element-ios/issues/6804)) +- All Chats: Fix a header glitch when aborting a pop gesture. ([#6833](https://github.com/vector-im/element-ios/issues/6833)) +- Device manager: Fixes from x-platform testing. ([#6864](https://github.com/vector-im/element-ios/issues/6864)) +- All chats shows no rooms in the list. ([#6869](https://github.com/vector-im/element-ios/issues/6869)) +- Device Manager: Navigating to session overview goes to session details. ([#6877](https://github.com/vector-im/element-ios/issues/6877)) +- "Notifications on this device" not refreshed in user settings screen ([#6888](https://github.com/vector-im/element-ios/issues/6888)) +- Rich text editor now always focuses if field is tapped within the border. ([#6897](https://github.com/vector-im/element-ios/issues/6897)) +- Device Manger: Device client information not updated. ([#6904](https://github.com/vector-im/element-ios/issues/6904)) + +🧱 Build + +- Remove the (now unused) FFMPEG pod. ([#6419](https://github.com/vector-im/element-ios/issues/6419)) +- Update build tools from Cocoapods. ([#6886](https://github.com/vector-im/element-ios/issues/6886)) + +🚧 In development 🚧 + +- Device manager: Inactive sessions screen. ([#6786](https://github.com/vector-im/element-ios/issues/6786)) +- Device manager: Unverified sessions screen. ([#6801](https://github.com/vector-im/element-ios/issues/6801)) +- Device Manager: Add logout actions to UserSessionsOverview and UserSessionOverview ([#6802](https://github.com/vector-im/element-ios/issues/6802)) +- Device Manager: 'View all' button in other sessions list. ([#6817](https://github.com/vector-im/element-ios/issues/6817)) +- Device manager: Add UserSessionName and Rename actions to UserSessionsOverview and UserSessionOverview. ([#6823](https://github.com/vector-im/element-ios/issues/6823)) +- Device Manager: Filter sessions. ([#6838](https://github.com/vector-im/element-ios/issues/6838)) +- Device manager: Add verify device actions to UserSessionsOverview and UserSessionOverview. ([#6845](https://github.com/vector-im/element-ios/issues/6845)) +- Device manager: Identify inactive sessions. ([#6881](https://github.com/vector-im/element-ios/issues/6881)) + +Others + +- Expose AuthenticationRestClient async login token generation method ([#6827](https://github.com/vector-im/element-ios/pull/6827)) +- Use unstable prefixes for login with QR flows. ([#6899](https://github.com/vector-im/element-ios/pull/6899)) + + ## Changes in 1.9.8 (2022-10-04) 🙌 Improvements diff --git a/Config/AppConfiguration.swift b/Config/AppConfiguration.swift index 52b041656..7f9e29b5f 100644 --- a/Config/AppConfiguration.swift +++ b/Config/AppConfiguration.swift @@ -30,9 +30,10 @@ class AppConfiguration: CommonConfiguration { // Enable CallKit for app MXKAppSettings.standard()?.isCallKitEnabled = true - // Get modular widget events in rooms histories + // Get additional events (modular widget, voice broadcast...) MXKAppSettings.standard()?.addSupportedEventTypes([kWidgetMatrixEventTypeString, - kWidgetModularEventTypeString]) + kWidgetModularEventTypeString, + VoiceBroadcastSettings.eventType]) // Hide undecryptable messages that were sent while the user was not in the room MXKAppSettings.standard()?.hidePreJoinedUndecryptableEvents = true diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 4cde3544a..ec3d81ec8 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.9.8 -CURRENT_PROJECT_VERSION = 1.9.8 +MARKETING_VERSION = 1.9.9 +CURRENT_PROJECT_VERSION = 1.9.9 diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 39de0496a..df349eaee 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -404,6 +404,10 @@ final class BuildSettings: NSObject { static let defaultTileServerMapStyleURL = URL(string: "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx")! static let locationSharingEnabled = true + + // MARK: - Voice Broadcast + static let voiceBroadcastChunkLength: Int = 600 + static let voiceBroadcastMaxLength: UInt64 = 144000 // MARK: - MXKAppSettings static let enableBotCreation: Bool = false @@ -420,4 +424,15 @@ final class BuildSettings: NSObject { // MARK: - New App Layout static let newAppLayoutEnabled = true + + // MARK: - QR Login + + /// Flag indicating whether the QR login enabled from login screen + static let qrLoginEnabledFromNotAuthenticated = true + /// Flag indicating whether the QR login enabled from Device Manager screen + static let qrLoginEnabledFromAuthenticated = false + /// Flag indicating whether displaying QRs enabled for the QR login screens + static let qrLoginEnableDisplayingQRs = false + + static let rendezvousServerBaseURL = URL(string: "https://rendezvous.lab.element.dev/")! } diff --git a/Config/CommonConfiguration.swift b/Config/CommonConfiguration.swift index d304f7fbe..a89427c3a 100644 --- a/Config/CommonConfiguration.swift +++ b/Config/CommonConfiguration.swift @@ -172,7 +172,7 @@ class CommonConfiguration: NSObject, Configurable { func setupSettingsWhenLoaded(for matrixSession: MXSession) { // Do not warn for unknown devices. We have cross-signing now - matrixSession.crypto.warnOnUnknowDevices = false + matrixSession.crypto?.warnOnUnknowDevices = false } } diff --git a/Podfile b/Podfile index e74068a7e..c0144e7ad 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.24.0' +$matrixSDKVersion = '= 0.24.1' # $matrixSDKVersion = :local # $matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } @@ -61,6 +61,7 @@ end def import_SwiftUI_pods pod 'Introspect', '~> 0.1' pod 'DSBottomSheet', '~> 0.3' + pod 'ZXingObjC', '~> 3.6.5' end abstract_target 'RiotPods' do @@ -80,8 +81,9 @@ abstract_target 'RiotPods' do pod 'zxcvbn-ios' # Tools - pod 'SwiftGen', '~> 6.3' - pod 'SwiftLint', '~> 0.44.0' + pod 'SwiftGen' + pod 'SwiftLint' + pod 'SwiftFormat/CLI' target "Riot" do import_MatrixSDK @@ -92,14 +94,12 @@ abstract_target 'RiotPods' do pod 'UICollectionViewRightAlignedLayout', '~> 0.0.3' pod 'UICollectionViewLeftAlignedLayout', '~> 1.0.2' pod 'KTCenterFlowLayout', '~> 1.3.1' - pod 'ZXingObjC', '~> 3.6.5' pod 'FlowCommoniOS', '~> 1.12.0' pod 'ReadMoreTextView', '~> 3.0.1' pod 'SwiftBase32', '~> 0.9.0' pod 'SwiftJWT', '~> 3.6.200' pod 'SideMenu', '~> 6.5' pod 'DSWaveformImage', '~> 6.1.1' - pod 'ffmpeg-kit-ios-audio', '4.5.1' pod 'FLEX', '~> 4.5.0', :configurations => ['Debug'], :inhibit_warnings => true diff --git a/Podfile.lock b/Podfile.lock index 0f0faf7a8..b9477080a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -38,7 +38,6 @@ PODS: - DTFoundation/Core - DTFoundation/UIKit (1.7.18): - DTFoundation/Core - - ffmpeg-kit-ios-audio (4.5.1) - FLEX (4.5.0) - FlowCommoniOS (1.12.2) - GBDeviceInfo (6.6.0): @@ -92,6 +91,7 @@ PODS: - Sentry/Core (7.15.0) - SideMenu (6.5.0) - SwiftBase32 (0.9.0) + - SwiftFormat/CLI (0.50.2) - SwiftGen (6.6.2) - SwiftJWT (3.6.200): - BlueCryptor (~> 1.0) @@ -99,7 +99,7 @@ PODS: - BlueRSA (~> 1.0) - KituraContracts (~> 1.2) - LoggerAPI (~> 1.7) - - SwiftLint (0.44.0) + - SwiftLint (0.49.1) - SwiftyBeaver (1.9.5) - UICollectionViewLeftAlignedLayout (1.0.2) - UICollectionViewRightAlignedLayout (0.0.3) @@ -115,7 +115,6 @@ DEPENDENCIES: - DSBottomSheet (~> 0.3) - DSWaveformImage (~> 6.1.1) - DTCoreText (~> 1.6.25) - - ffmpeg-kit-ios-audio (= 4.5.1) - FLEX (~> 4.5.0) - FlowCommoniOS (~> 1.12.0) - GBDeviceInfo (~> 6.6.0) @@ -132,9 +131,10 @@ DEPENDENCIES: - Sentry (~> 7.15.0) - SideMenu (~> 6.5) - SwiftBase32 (~> 0.9.0) - - SwiftGen (~> 6.3) + - SwiftFormat/CLI + - SwiftGen - SwiftJWT (~> 3.6.200) - - SwiftLint (~> 0.44.0) + - SwiftLint - UICollectionViewLeftAlignedLayout (~> 1.0.2) - UICollectionViewRightAlignedLayout (~> 0.0.3) - WeakDictionary (~> 2.0) @@ -152,7 +152,6 @@ SPEC REPOS: - DSWaveformImage - DTCoreText - DTFoundation - - ffmpeg-kit-ios-audio - FLEX - FlowCommoniOS - GBDeviceInfo @@ -176,6 +175,7 @@ SPEC REPOS: - Sentry - SideMenu - SwiftBase32 + - SwiftFormat - SwiftGen - SwiftJWT - SwiftLint @@ -207,7 +207,6 @@ SPEC CHECKSUMS: DSWaveformImage: 3c718a0cf99291887ee70d1d0c18d80101d3d9ce DTCoreText: ec749e013f2e1f76de5e7c7634642e600a7467ce DTFoundation: a53f8cda2489208cbc71c648be177f902ee17536 - ffmpeg-kit-ios-audio: 662ce2064e56733ca7d8216705efbc38d9e1c3fe FLEX: e51461dd6f0bfb00643c262acdfea5d5d12c596b FlowCommoniOS: ca92071ab526dc89905495a37844fd7e78d1a7f2 GBDeviceInfo: ed0db16230d2fa280e1cbb39a5a7f60f6946aaec @@ -231,9 +230,10 @@ SPEC CHECKSUMS: Sentry: 63ca44f5e0c8cea0ee5a07686b02e56104f41ef7 SideMenu: f583187d21c5b1dd04c72002be544b555a2627a2 SwiftBase32: 9399c25a80666dc66b51e10076bf591e3bbb8f17 + SwiftFormat: 710117321c55c82675c0dc03055128efbb13c38f SwiftGen: 1366a7f71aeef49954ca5a63ba4bef6b0f24138c SwiftJWT: 88c412708f58c169d431d344c87bc79a87c830ae - SwiftLint: e96c0a8c770c7ebbc4d36c55baf9096bb65c4584 + SwiftLint: 32ee33ded0636d0905ef6911b2b67bbaeeedafa5 SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82 UICollectionViewLeftAlignedLayout: 830bf6fa5bab9f9b464f62e3384f9d2e00b3c0f6 UICollectionViewRightAlignedLayout: 823eef8c567eba4a44c21bc2ffcb0d0d5f361e2d @@ -241,6 +241,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: f8957c6eb2617c1f8a9174c1c5eac5f2bd67a599 +PODFILE CHECKSUM: 34adb69712a819559b32481fd9abb6e340b23b0d -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7b45c9e98..482bfb1c9 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,6 +18,14 @@ "version" : "5.12.2" } }, + { + "identity" : "matrix-wysiwyg-composer-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift", + "state" : { + "revision" : "11dad16e3e589dba423f6cc5707e9df8aace89b0" + } + }, { "identity" : "ogg-swift", "kind" : "remoteSourceControl", diff --git a/Riot/Assets/Images.xcassets/Authentication/authentication_qrlogin_confirm_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Authentication/authentication_qrlogin_confirm_icon.imageset/Contents.json new file mode 100644 index 000000000..3c15fd8e3 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Authentication/authentication_qrlogin_confirm_icon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Secure connection.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Authentication/authentication_qrlogin_confirm_icon.imageset/Secure connection.svg b/Riot/Assets/Images.xcassets/Authentication/authentication_qrlogin_confirm_icon.imageset/Secure connection.svg new file mode 100644 index 000000000..ffbcb3b30 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Authentication/authentication_qrlogin_confirm_icon.imageset/Secure connection.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Riot/Assets/Images.xcassets/Common/exclamation_circle.imageset/Contents.json b/Riot/Assets/Images.xcassets/Common/exclamation_circle.imageset/Contents.json new file mode 100644 index 000000000..f6d56c99d --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/exclamation_circle.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "exclamation_circle.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Common/exclamation_circle.imageset/exclamation_circle.svg b/Riot/Assets/Images.xcassets/Common/exclamation_circle.imageset/exclamation_circle.svg new file mode 100644 index 000000000..5d23e58d5 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/exclamation_circle.imageset/exclamation_circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/Riot/Assets/Images.xcassets/Composer/Bold.imageset/Bold.png b/Riot/Assets/Images.xcassets/Composer/Bold.imageset/Bold.png new file mode 100644 index 0000000000000000000000000000000000000000..186ab7a2684acba5bb6430ca14bf3d15a17e1139 GIT binary patch literal 470 zcmV;{0V)28P)mB*z51-NcE*Fra(xQc-KVV{UDOrt=El|RsHx0F#7{0^%(&t6UH zYb^vE3jkc`s?^VlfYIb-&ouRDT!Bg;bS;(x$wWh>s%Acm3YvlaZu=yVu2o2YNwog@ zOq`R*G>076EyEuLm(F-P5}Qj6or%X$Uu!-1vk>Buc~;r4K_8mx@F9ajA?S5l>Yr)l zjJ=upq{dPZQ`q{Dfz=Q&EaJNWd0*mVRRHo%qW!90d<~oc@N=mlMH$JFmm~rztLjV8( M07*qoM6N<$f`j11SpWb4 literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/Bold.imageset/Bold@2x.png b/Riot/Assets/Images.xcassets/Composer/Bold.imageset/Bold@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0f57f9bd050b644b7fc48154d6f653a630438ef6 GIT binary patch literal 737 zcmV<70v`Q|P)f4nyUz!+nUF~%5U?EVp8!BI3HAZA?<;=x7_KwJnR zQi*Wxi`A&tZlzEV@&Lk*C!s{_!gi(}tcFDd;Q=uzWfQj7lu$hjqc{M|$OG7T+TScF zA_V#XXz37Qu*mfQw8n?%xB-?_pdfGBd&UhnjOIax**kR-yvQ5(z#FJP30UC74Cu9= zrv1)-6H$&0mo&mSrrKNn;nWi7cb<23O^`Q+%iYIdfpdER0?zXOpUJC7V1au9AS?_6 zcYg_JY9kB`0}HUg#Q<{Gy6;uKg9T0naP(=ekGmjmOdx&(3ks_P)_JeW-I~~WrTU26 zCQ5CLi9Ra9g2DngjK1Kyan(T@$2`xQf+b}rg_GAWpUuFMQuG39_Qdz15?rH+m(??{ zpezBXjS%XGlammLl_r4S1a_%gV<+0-YkHk$7k)zsUw_g;-Zj*%G5JkV({Z;+0Dn7} ze4ZNb57br4KEo!$fyF*OX3)dWrlP(*9& zYykTEfFfFBX9JK}H6Ddx=i(fQIMEJY({;erk4jxsJpJbDhRdc@N1*0RJFLk|H6?F& zMXC@Q0-;is!s?d6nEJ*@nVO-m{m$ODU%UT@J^(}Yw%t?W%&)DUfCc1xfD}0PKn#F_ z3R40M-*)y#V2PEV$Q(?LM*>tMbGzRPq!MHz&_DHemkH&L&lqEjF~%5UjQ#Not-JowEoWS-3>eBzrMl-sA(csp0Wg;RNqo^A&1h#ks#}kMraE^c* z9mkn^R>8y=W9;%z_ba>JCz&*h>HzhquB!Kn29#1tDW#NBN-3q3Qc5YMlu}A5RSJTj zwav{o4`&wvUSM#FKf!zeqJ3t(Wil_9yBGWT2Z#m0b^c`7dS!orIh12SO!Kv*qy!ryY$F9YoPw56GkuOX~fBVgsxc4n(zui#PavHOJnG=(85=n=5;csuuL zN0iu#7u=>QgD0B{+Cl(=4uI{X!lc@78r--V#%YNt_W_dz&L9v`2B0yfV2bl%2eVc; zb{K-}FTPJ0o#}Nu0|<&X6=9=4=T=P8hqhwR1*MFZ*Pfv)(I?~FNLYQcy9Obe4Rwf@ zY%Mv1fS~sQ#+W&FD7HG44_y50000P4R$dw+aM<^+s zU12fYuWGAj=h4&qPfsrp5fKsb57e8j?RxWm8zG2eO1!hn6l*kqzPuh|LKLQiIk_NK z1CI#7p9<8^?+!HcdOwEj{>JL-uL&_M$?i%~d)W1*O5%km*4WK2SA+ZqDj;WTHm{gw zD$!Rx=Om4c7-D)Jc6}h80?~{HS8AuXI|vhichX90d$vYqt&7F{a`)$)qtk1}rKFbFI@UQbPZLT#;zfHiG;ae)s0Y{hULhy)cf{Ag=Xe2pkCN|@cX64vK>}T8at_hxA!rMC zr&=_+_RN#1pWPj}=6t-zGlOOaKFAFM*(B{+pW_A~3B{zdupi*gWF!BQSO#A?H~+pt zP>ifAiLD<5P&>cb0h1?fQwQdTQrjd&^361RrTpsb&vhf?_6GSU@gF%LnQXqWD#tNe z2S%#jvuoWuCufZ52H&7B0u}J1)@-$<0Fk2g-OZINOhLGRfus6`CDh+g&Q)xw;7+WF dh=_VN-wcSTyBvYufE(1kL9=Yrs9{O6C{{$bA=^g6psFJ2hEx&BlH34#gC4<0$S!Fr zW;)}5G&r`Ie|3oTek=UMjLq|#?|m}>AP9mW2!bF8f*=TjJR_&yE`!tIWdJ2YVMB<{ zt|D5cQwnt28(tqnNo+&{qG+_uIBzenIj}C2#6~1wzEug-YtG>}l*EQC0s1Z#_yr}g z5eXoKyiuuhY%cbu7KY8^>HF#gluzw#+=vMCgTZ*botNnzB}htId`xa*!IyaoD$GG8-M1^ zRk2sgpOA`n`O9w6JHPq_4p=83`aW(5weF|FafQ&n_8g+5#j;HWWnlM%i)N2lW7B-u=dEb|F&&54NpH#oIU75o~ zJ`-4AoPcN~ObgVeuqs?2bRY~5cg^}Yg_ZK9lwPbG@1#JOgM63TFwRSHUuoE>V1(@B}A~3`#0aEdSq*R;3P3Lg0 z3x;I5Ka=GnT4k4W{xcQcH|-4C3CL0Wn7(>wf)(!Wukz<_o1|>BX=l(*fOtdc{vL~+ z+uI`1D#C~R3&Z9uA#?{aI)mmE21|hR41u`^8%*5adNQ9u`=#h7z@ze-xW6I_I?_OM z3jGB5R9+YNr{bd{8Fc|4%4_QW^6=G>jD3Ks@|wE8EPQq(;{+5bua+mbaDOU!dl1`> zWVIKt_2e$@FB$J?53({AEXniaMdtyo?r(7(P*-DCdk_Vu^MI_*u;zAN&;D%oAZeUW zgdRHsOZu)9w4(C>SNA8+14I`}-nOWofz2M|$umJoNcSiI^iV>vsG!&I%#?O2?92W6 zro>}$Kkm;rf2VhE* Ur}4^ok^lez07*qoM6N<$g5x)q@&Et; literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/Code.imageset/Code@3x.png b/Riot/Assets/Images.xcassets/Composer/Code.imageset/Code@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..830f50d7e6eca7fe2da1c0c1ad626d7ba973ab74 GIT binary patch literal 1089 zcmV-H1it%;P)x z8c`Gn@N)-Bm*o+71mB?a35?77k>aL}xDevUR@~MKxM)!~*cfza`vj$L;3Mz|t{P48 z-rkFCLUE={opbJtIamMg|0_7%tS;6m!)B@gt)ugnHl{YdO>4cky}r_*j0$Lm*cZDpxh)8S4a%@Q&4~TZ zS=lb=B0VuPvjUV+m9XuG=sRWD>=Zz$mtot3KwVOX%|roOC*3*ws}#0RX@wOaZ8&^Z z4%=?X^y_P{2b5vnPQsG;JWy&rY`Zb^m10V(fAF#bTs}9X8}>k5y#Fz^{f}o2V$kYz zOZHbeY@f-iK0WgB9dvqK@<6-*&Et#R$>wNu$4vQL>q0qV*I?)E(iO$Jjfee%RdK6= zgozfQdDPo5+P){)PmJ6~G-Dj5dv^{r33$nK)=u4&6*T_U&tB z4x{$j=g@wC(wie8A_Z_SzNW&r(eX5dZH}(Cs!KIWXw?0N-^ca#+De~d%HIsz%23qd zs+hZ@%irf3cI*Xcb-TsUiyx88twD~01!BO}{nqQN*Gi2Z(;(tsN`?xT?zs^&{uOZh z+?}{eq5F)v4k&oVmCZT3U-y{qG%dKt7`ERQbxz+DTEvXC08xqO50eX4xn6vU?ssS1 zrY5ESmH*k35nBPKB_21I-+H(Bg_uZnzng1Ir}hNC7qy=|8L<=~M&dc-z9_=c{YJ4T z@P;uaGDUGFqr?U9m3SualM;5n)8uY{)_f$RL1p^`HcAKh;%Gc95!HbW8ae$$v~IqU@xOGbj-Z(1`gN2C+x zo}q|$ziG|1NN^OvIYW`|ep8!ik?`k&rX{1e()#2xcj5^n{>iwgWE6LX;-mWwn`ucT zo-kfM8@g3pt{9^aN9P3l<5or7MB$_Ry?>KD?(rp+`7h}mbb2nGlqa{_PPM*WU2fA8 zm+9{Z`09Ssl<5Na=zi0d?Qh6;r{kTUcBv00000NkvXX Hu0mjf%n}XQ literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/Code.imageset/Contents.json b/Riot/Assets/Images.xcassets/Composer/Code.imageset/Contents.json new file mode 100644 index 000000000..12e2e8f2c --- /dev/null +++ b/Riot/Assets/Images.xcassets/Composer/Code.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Code.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Code@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Code@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Composer/Contents.json b/Riot/Assets/Images.xcassets/Composer/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Riot/Assets/Images.xcassets/Composer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Composer/Indent_increase.imageset/Contents.json b/Riot/Assets/Images.xcassets/Composer/Indent_increase.imageset/Contents.json new file mode 100644 index 000000000..4643bee9f --- /dev/null +++ b/Riot/Assets/Images.xcassets/Composer/Indent_increase.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Indent increase.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Indent increase@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Indent increase@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Composer/Indent_increase.imageset/Indent increase.png b/Riot/Assets/Images.xcassets/Composer/Indent_increase.imageset/Indent increase.png new file mode 100644 index 0000000000000000000000000000000000000000..7cfba0707bcb887d4e0df389de6d1e4dd2c04833 GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG z>1#Y*iD_MVKo&%}$cs^fO=hP`rwynj(Xs>$6{niX&}rCH580 zyQvmg(kdK~!l%shX5#YEJJP>eOD9-7Ks9dos8B z!R!p<=0-#tY2(J96K^QAeq6Eb@^{@yz9Ok=C!c5cw6>--q!GFvFDCysx3!&8@{WG{9oKWVGrZ6J1uX; zc2?g1`a)bmNl4&%Lr9mUl0Tov(izG|Pa8sZStfnATu>VozrBC9=8`Q%E=ha>=NsCu z-m0IwVB_bTo?9JNeu{o5*cQ9@-r^uF;|0&uR`4E-nq9!*df)fJgqa4nWIo(%VT^At zeBRK^c>Kyd=6{doMDtY19G}Eo*M0PU?A+)Om$#fNHvaHf%D7cbUe$cTvqy`k$P_$0 zq{4r++s<+6lZG67mqP-z+`BnTO15v_oXqi|o}oxng*8o|0J>ku{wLfPQ&)8=@^B^kWuN}PUER6rMe4q3T|hJ8K-6stkDC7W zYx~NN%t-E>xiw$u=9^uWWtvR?i>r3hj)NUTOLqddGe>H^lxQGXsN+-qzF=X8Do9J&_Z( z2}A~mfGOd~fJKkqFfwjhwqnzQgC!TmvZp0)nHg*9SH0*!^tB~(SXYWXsN1x`p8rJA zTBRipwH%v*p5&d>(0H24w}4}IOEo&$Ks!_WiY9; z)Ms7r(L7k6q4&S_RGkyLw~bD|YK_f%x;0BV@W9Rm4Hn`e5|IL0hj%W}b^R`Hn7eWg zHxmPch0h)FFXHdGf64FA*D%<$z@;$$!ZaXLWRn6sE`Vv`3!~~W^Rl!LVIM%Ep00i_ I>zopr09jk&@05}0~0?G}#Lqd%Q=f_ng zL~+Kb-T0F%J8?38nt9O#2m}HvMu5TP&HXuPeeru#F*+R_?!f^G21Gjk;0(kw)*m4kT1^GT1LVhTxUJQj(gbdMe8gFz^yU zB&>kR+3}Hc+W$>}lybWk0%9HREnwz$5^rF_7El?`IB&BKBGn_9um#K;P!KIY1LoMB zb}>nZ2V*c{3lKtXRkM!)y#!bTlBQ>Pz!H$%=CL)PQtL19fF+=us>rUz#-~>XoO?lb zbr)+OZr|E9BdOOK{c4i(>nR=f$LNT2H`EdjW{Q4nwB4{ouWCdII^tdevAD&W9~7^z dArJ_xjBkd2TX0YKvUa zUOka2r5+%YbPKY;DO2$T@kKd z&1cg!0x}S?__mx_=8uoLc!ze$4WQWH8&$`MIurn`-|A#2gP^DZ7-ODS#|q+e2#Pxcn7t+N<=tU7mOyfv4!xg0 za0X$~0@!fRyZi399>O9n0UB3FjOqP@uxJ4gnS<(BVK$c#7Hop<~4PaY<6)_9Y5e=ZO0NL_?L?jwO?e364 zhKQs)z$?JL{h~WdfQYI~U>$m==b?h{z$9rp*j)8b%IEtBl>-gj4sYe{?&9yw0Ae~j z{_y4(gai*@@qPKH{MMIfz{0-xJbRl#K=>ul@ZucbX%KHVV2O)wDJKn=vE!9e366w-17oD&dEfVqJyK*@Uk z>>!XVD?5&a*E7$&UlYlb#(ws^+1U>fA|fIpA|fIpA|fIpA|fKHgptH`2ZK%hS>I8e z+!tduWr2z}eoinwJtu@+EA=URBYdR6%v@ zR67sbTRn*+`DN%|-TqOQOPbxOm$Lp6)N1aw{ED^y3Q07Jd z@N4lWJ8#T?yLa0+qkX$CBKavOq}v_%V}q>pAB>(z;-CY_>uZhB_*y+@0F`p(>vvT- zi9Nqx1att_+AF1lM!pFE>|cOtqkO2MrR##h2GH#fvfu_CKmmKO0Zg<8U%(#l6l9uZ zBPa#z!3JQAy<9#f5Z`_e_ZJW-;Nl4Qi-3R(p!;^vScL-iAOo1>wJbFLXB!&==oAz# zU;zRdK&XHV#{vZN7Z505TQi>p2wVX33OF}7DBmDX;bz%@wY5(I1}cEx1sssu3B*a5X;1;UvyD&yW3&SHpaM{%&C)T!+5ee_ z)&VP3z#dcp#+dRWV883WUZH3)sW?1*loM0`_16Fe2rbpnw*zhtvV31*}e? zwxFj$1mG{i4Ha;$whpxgJq;p&c^Batpp)>kFs7h61>D)bJ`zw60a$BFFTz!@Q;@SD z0w_v0D<5;PD%*O*U@LhNN?~Z_w3T`g(WapNH%EJ_(yQph z_nJ;NlHa0^7C^Clyerkq=51rg7PEviSppk4hDbro0EV&z&YF=VEZ!8PlVv<2<{G;sD1lm@u#I)!O0Z=7@A^vYU`&RAtE9oA|fIpA|fIpA|fK9|I;rL90_i~Tqr64 O0000%;2U6{0B1?Q)&`fdaZH?S16z_fW)YjL><|n~as!JKU~V98AU2dL zSkv_AlTu-jWCR5Zy(*Q*Go$X;-LG2!IdbIq$3XZ4xEPJf^tHH00;)vth0U7X{mt7n z?`9^@yL!7vuIvM1`G+G*z+Js#=vLhO0y5zIa?&J8W6wE}EkTY6$PA7v5{jcF8<_}5 zBrj?Ln2FVI>qo|p-Zi%Cyrl=d)|h`xwMG(+ zI2#365}x3kQky_ldl)URF?G^J#1I`d2nn zm!Nm`?qor51YxUHTq+)T&N)La6yK{obq^(eh_jn21HNpSr#NM!Rdwcqs~;P?<4gp4uO@Adv>$a24wN~?C$0uBkDqMKAdGxT=ZOToTGGl# zmG)@lXM>wQqGtk=(nlO_{+7;Jwotj_y~k$I4r0K2vo4JNYC%c|ycM_N4n z#*hKAZAf_EW;HbfqRy4z1c>SB;LQPKKw=kgn$f?yfsa3NA;E9RDT014=#${P!3X QQ~&?~07*qoM6N<$fUXC5V0Z8tgseFNtNl}}K6*&2nVO$#TBL@64etJtdmNfWv#QnlGgMPO&?_yo}n zte(I;g1Ts{O5&T&Ff3wG6fN11=AZWfEEbE!VzF2(7K_F5{{vwI z@p`?+^skzWN;l;HN_{r$wA&*nfQ<^k^OvVBP;kH>oAG0Op+g`%d3g8B-ylaeA^_gY z)9pY(k1u9>h2ehSz}};;_lJ-Jr3c{IPp_NG!E-=ttaBbJY(4zOd^VICfFFK7+eM^v z$cP5L^WEWB128~AiLmotlAVAC>|I+NBntM3DDh4!P(4YQU_b>*!t1_q*+DmE_Sx1> zxT|k@{1G2}$DgmC$>%$|bV|$kK*L_#t`I*514?}a z)OaEewYApA;(Qd8r98T~KTLQWnkJ7yX^()=>4f5@zs@;GIN;QMaNRbY)?5&skbzqb zz>{vztVcWmy`y_br;!TdqERze(0iy8qxOU0TA3=h=jd1zZ(onm4(S7q6Zba?I zE!FD8hl~^!01-JFDPFcxkwB#dK>~`12lMC9z@$+sBmggdJv#^$-kS1OkRT*wb&vCh zR^Q5W2o0o(kAQRDht07B=XmNc7u(|#b=B$(p7 zuUN!%<}<#}uK_E(3nG0^_`VY)f*(6~Kj?v_&J&FkIy+!UZp~1t^DBs)XWiFL(rR?Q zET%e7xYN03-V)~$fRwIo!TG0>$atmarY`O_oEMA(5MC%%FB1~pg7e-lXC8~62hk4? z5hi;{wLe@VsHh$k?0j?`X0VXCf`sI>;QXuJwhn5S@o}i(BvAVI8O(f9iLCW@An}P#?(>Mhuq}oQSlo@n5JClkQ1D&I@K`P{G2T3bTDe!V?>pzGGOI z0-YB!2f*_}vC@p0`)L=-iSKC5AoxW(FJzwqpDs#VMCa{Ir%4ca%5Oz|y`MBj$BF!oWpxxJ3>|Qw)aRuh13dMdkohoY{!8 z`kYN^a$!v(;Zx{cP9u92_>-q^J!VtRXRyKwk7$RPuCyFBBZiC<>Dw^%F|i^XEGSS%KcuidC9z6XY!FncAkGFH8+&Qx1eQ;L zIDwTLC>sn!dY$f(6sqb@Y5fI+$Xo=Vc6Of8 zka|a7W?))S9q*Rb%McQ=4?v*e_aRZ5JH^}zgn$Jx#)S9`Ez-+}XP4&?00bwdkTZZHUP=n&N1{hQU8O*-+!`E1&@@=g|<@G_b$^> z`svxrEEpoT0bt=QuvU34r&p7voVo_R>PP%f_o9)Y6`nY?E3~}ss(lmVHJ~RPz2zYQ zl8H`TcT<&-jVjnQcbHi31A?UdW1;CZ4bjHzAs9jeFoaUd>mDI6W_k+xg*ID@@L-cN zo@L5ZmJu-XsgZlDQcYTP-zTlkMG#IVttDx?CxppNdO^0OoV0j8&ieRRgBIa~N&Wr) z&iccsHVS-WulRTs3@}q8z5g!cqekPItcolz~v9?$Gp-yjgGRvye zQ@>b5oqDfF@mus9!T7V`(AR*V#2etJ8M{gA5vll$(GlR(dk1(~a1r%2Jv7g{UDFra zz6N{Wg9joPpE0%%;NDM9m^Pi-y=FTJl^j|9FeNh~K4UBZw!iL7%WJC1BC%KyTo7hP ze8yM+P-&-XGfH?_kaBy!OvAmV3tygNv|Z#c}%AKQ5O+urW3Tkp=KKEyN?+)W4c5ADS=$ zw#BfJ*iy9Hr8TA*)$!7xFgXJ1PHkLs@4IXSdd3?0JMU~+x|pUc zTDaTffTlg#$;?c7JAMh}k>s<}R~K&1=oG|Sd?vJq@<*qaS1!jvCn5IYGokH-cS^Yx znp#dE80JEJCR8h8ue8DGVgaV`_W1zztXay_5XlJ{b~3xz*`@{eJMdbVYpDj0ps4@D zRnSC!M2QbU+vjk(k{Kbb`q>Zp_bon|!l&4+ukPz%lnDxgAP9mW2!bF8f*=TjAP9mW h2!bF8f*=S%@gJ$o_XBTJb_)Oi002ovPDHLkV1ms_{T%=R literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/Numbered list.imageset/Contents.json b/Riot/Assets/Images.xcassets/Composer/Numbered list.imageset/Contents.json new file mode 100644 index 000000000..03c78408f --- /dev/null +++ b/Riot/Assets/Images.xcassets/Composer/Numbered list.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Numbered list.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Numbered list@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Numbered list@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Composer/Numbered list.imageset/Numbered list.png b/Riot/Assets/Images.xcassets/Composer/Numbered list.imageset/Numbered list.png new file mode 100644 index 0000000000000000000000000000000000000000..b2798d0d5855986c06b6414b399c412e621a7fd4 GIT binary patch literal 287 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG zF0iO$j=cxYPICwSX1;XcXYzXBnW_kA6= z*q=Q+H`7c~PvJyt(^*B)2`hJU2CPcAOE&RMnW!Ln;F6hC z(mUSZ%RU#2Le&)51SdXTmDbp^dFFz3&l?!}wD$6(rB_+ZESdcD+-l8*%(|C;-#q_c zXTu%Yb!_XuE~u~k^E&d*YaM~!_;;pXW_FzU6|SC`ryjrA;fu+Bv9->VTkmiCW|zlh zZ8eECCyepr&&tJNjS?;{^Af}yY?&-hRmCd=Z?S*iCAOhXJ)%If=6Ac!g>}7x@4vHN znjLp>+l{W{kqX*8o|0J?9FfgTfx;TbZFuuKgu;`M5%<+%)N$nqRxIYN| zB4~4sZ=2k~<}2Icded_!HZM{yuv+Ub?|A=!`vmt3(dkoSHyiqRN_#E4YkxCy-+XRH zCeO0NYP;>jSb(;`!GT!ePW9vS_wCtqOy#J4|77vIYp*vgP5Stx=wbT$lUJ_?JXy=s zBXRDF^oMIKx>iziZ~gpKSHAm_S)Y5Ra9Vn8=OSOu&aL||eJ z=1edRUnsio@i)y)f6d?f3k3?euu3GCsTLH<*o(6|hB!=a{m9+%CV(k@ZH*X^e;{%D z7=Evmv_$CX!fFUN9gf_V5?-u=WEwb60fuBvy0jKSS3kr z=Vr^=iU-ry-gqDN_(gf^b*_T#i@1GHe!2J8zJFECx|5wVj@KQy*=Jfd$$suJA^SV8 z^L$s={d?O!xqPCFwPIY$UCu{A)`1V!GWEGFw^z;oe%jo{eNE4KwH38@XO|o~wRWwt zn-ws1G;pgVnJNpemMlJ!wQO3)+0eQA*RHtuJbnLCukTN`S(*0w`Kn3BME9|%TPtp| zOxkZMWH^i5ZuqA-`(J3l`7z?u-& z6{0~&Ox)K}96$CHmCs>N$Za@)-2ih`Gg2*O-5}C+&Wlg9WuTgrs8{#rRO`gt({<3z z*4}=r0c;_}s_U{5GwB_ekB7rK+A@%5GSsy>sgPo)G6Hroz14tiQZ6{GD{^xFC4wfA zA)c9ez`(@RNn*k|Qpvc4ha(UiA-+wbLZD2}shOPD56P)I)1ypc*p!?Cj*t_N6!p~? za%k%6;$En`6w#vCZFKG&cDDU z<9_eXF6BGgo3_zF=C8JYyKkF6c6PemQxJj$CNC!Mwh&?L@4QuA2yRf>D1T?+!^O>4 zbjoiV0}upT06^1T%H|nO4paXpoiiG|34&k?U;xv)EwPQyG2a4;H1Z8!K@fZaj18V( zVat=4uXQ%9+t9gV5CmHQL~PYn@$W8yvPjavh~^Xd0mQmsr;=)b3YGu}kxh+Q)1y&#UMI}? zB1C=H2}=N9HpZ4fG^C4WBjO9uY_U#w0vL0nZo|aJLpo9%NlSqhyetr|X)*;y3#R1S z5eS1PK)9yKCu*z>qJ=7$)(LMDL_C;(?>^GbQ2$>}Maw#2ABSH~rX645Z6-FE^&j0m zSjN}CJn{K|EG-2pilVI46a3SRvaE$?&28UqOAVS?|M3-@?cTKG^V#MyzIFI5vA=*j z+c>Ivjs9f^4ek$Ly?;RY4&NL;_ji$9`ufb10RwRYTpYR6Uv2)zwkUsBmbYrA4locW zKv6b=9)hnp$v$GeLE~Z78uFGv><~pAxg!l4@5`z-#R(8QM3G1CNVFK>wcO+h5a~K| zj@%BB?odS#7y-3G3NVk{#EgPMlmJy54eJ?T(UF@mqo9x#0oTK)zo_fXnNd)P5`f!v z=FBK4L z&S7LA6)lSpB|!4M7+f}9eL6h04sKJvPkQ%fzTdd(Rr^w1z5wc1Hc%8rQ4~c{6h%># aEBOgr6Dj7Gwb+UP0000LaGVKJC2!pe>AXZKU!Mo1;u!|x5`RAF z!-mv8Gjo)ZFUF`Qc5uB;hZGsTA&AYK(0bTzHvOHa5u;+%5@1d$ma$wlvri$IXG_PZ z7`+4-dqrsdN&#$AK1S83C4i+*ZoqZnuFOp-LOMf`LiuQfdr0m{tC>=S zR1%<3g0hfAH^mX<@RQR?fKY-OGAV`pAyNry=nF+k=?W;EzoCrzfC6w<0^}=!)o5nZ``b+tHfHXy5IIQD@81DWObQ7Gmtveqx%GS@mo@Lp3XpE{-+ZBWE($w~7rNTxwDr?ozCO|45-BM@R z%~Lq9fGfbGZ235?^9oTWK%;#0omb$*s_a#YG65Rpqwl`Md(F3_m~Khax1NSq?29g(I_CC1@_rDoCSzoI(kji!K2g<>NGu%*J;G)azpP z5>!53EI#p*(>yX8Ujhb$LpOXBb#fY1K7MnRk`P1l$ZT{8Xt&)gXF_8a+}#vJ&?34R zgL$I=f9DhJqWs2|TTCZfrIQ1MY2I-wOSgLygg6z0iT2(6($1tY000000000000000 e0002sKlu*$2(}FC*QUY%0000oUZ}wF}N_>?h{k@ceS(+ORN+K zI$&^C2-Qr4oFcsYD92PON zTrqw1*VhIM$2;5Y!xoXMi4qbXv`p_cRzR#RYJX{K{q-M%$H%$rNn`qP#bsas3(OUm zK?&c+_sb(0JXm7>2Ne3VS@Hi~Zxzo(Y8L2}8;{}6*AA~-fM~w%Mu_$&m<-eI$mcQg z%dvNsk{W_&>|8jDDfduaPeFMO9HifYp$wmr&aKk$B}nyv%HMoh48c>-A;4S4xD;=< z558%K{i&8tlVqB^J&zP^>RPc;Su4W;qdyP?p-YrIA>vSHMv`1a?CJ)!(4?M6fw;&> z{g^ literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/Strikethrough.imageset/Strikethrough@2x.png b/Riot/Assets/Images.xcassets/Composer/Strikethrough.imageset/Strikethrough@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f01b9b897d94e882c5de6ffb163f780b4cf3aa71 GIT binary patch literal 962 zcmV;z13mnSP)VBqtU}dNw)6yq6Eru#N1%&J z6zVsfmjW_QA|>RC{& zUVtE?0%S10i*pT}x~fQuFabjBemGhjXgtvc@lt0CyM`g44}IK&pP8o1$p#-*K?t!G zu-iO3hhC#X3%0tk^s1eFey7QC0?Za4mXr7RHQhzLNI_xSs?gQXWC42eM# zxHxh5_sD(+Aw&c~#OZq%Y;*=A;KVZM%~)%<(ct373a<(w#CCzK+^m=y2qUus+!hyu z(3^%aMJ5IybmHfTu+V6=!~$6sLJ9R%b6Kf7CyI zTYxblQzqE0@{IBO-ZbD0E%&|W#4)Cg5w%&EpO67-Y?viWOf5jxLMK-UgZ1UPJxD=n z7oaFwg>eBXb*e0r3jar>7r=vU7!x>S+=rB83xckMwF81+L1Ybrit@lIbS^L|+*Uh{ zpCfvEYj2tOBa7UsBXTV??6iU>-ZFFtwqtbt1-q`fJU?Bb0Kti zd+tTxzTjg>iRF};n=2qmzKM|6)4MCVtrAj#$*wYD?(>0s1^C5{kVF;89o<%%GPwTf z=-ut?@D8NZ?lg#81qgzw8+asr)OTgDa}O92S7mZzwU)RnlJMjn1U>X>mZI6j*Yhn* zo^w<`j4bSLuJjL#|E9;d$bR3`GvIVrWLPQ^VRjF`u7btJzz3Y0Aa-IeQHqbt`w+4# zRYqy84~tlc<=|J#Wun7kxfZ#E@eZ+axI5u76eNS&kk=H;IIdc3?x z^IsOt%?RBrF<6<;J@UsXEER4vk2w(9GSB&VZb*K2kH7W#n{heuHpk`lnR#?|BK?mM kilQirq9}@@D9S_TFD#c(;D6=yaR2}S07*qoM6N<$f&g@}>i_@% literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/Strikethrough.imageset/Strikethrough@3x.png b/Riot/Assets/Images.xcassets/Composer/Strikethrough.imageset/Strikethrough@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..22ccc6db5eb449a9e1625a8ea30daa191cfadc4f GIT binary patch literal 1354 zcmV-Q1-1H#P)SI5jRjyU~z(wr41Bl6PL&WFpw&*5UW&~7&(=~*x3k*088)*ET16u z378v%6*elSU%Caj{GmY-Ow)~czbcB479&-^neLu`-2(_Agb+dqA%qY@2qAq;->#&$!u2+_Rovfip^VNs3`8Kk;v0YP8 z%PB7_oF3=w5Vr=;yf;BiJeyY5Z{>f<+zr>5^`B|wr04+x;57jCdZR!@rIh0Ms+E6X zac0t<$hLV-yKbM(NT=R_`L#%Qpe%H z2R3Ekiq`-TbevX}_{Lo>SJb=ybRzlD3UPTZYu`Uy5UuqK5Ceg{eK?#Pl0NBJj9}iEN zCgy&(*P1zgGbf~G(Vu2}847EsQ_2xVhb{x|Bmj;Q>pnTlCp+nc6CSEza4O$&mdbPz z05iorg~3yT18#<)%-&Od4meq@;U)k?nayMdWD^cJ34j?=+9XJf!C^XKV&dMeiU(wMUncC@~y29R!|5bgb*S_w_=Z27S>+v%$mu&j(4-Yt~+(X>#7JlW2%pb z$Jiw27iS(Fz@bDlnKb8gr?O9FRQOhvXgNB>==xY9*hq+jsDuo!kHR zd*U>PCtd=elxkj5w*2hxolNK6ZQo*Y6lwbbj-GnVFMA2V`8v&JT6vUUKhqAG@;2XG z>{P_SJ9y-CUC?FA!`nIKwU@is&aBm6?ba{bajz8Z!y~UZVrJ-R6ZhpVFWWTJ+FSiT zfTR7KiJYp_<*CGYp*lVSkG$T9B^vXQ!oOW!Hf@q2(l7(Au^TCgi@~$_sD7&&UJX7ig^C|pE+o%}#uFWcysF_=BxIhg@084NwZzP2^ND{TP!Zn; z#SByVh|RQfMC$zsx86J*!2JY52qAu^TL1t6 M07*qoM6N<$f<8ciwEzGB literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/Underlined.imageset/Contents.json b/Riot/Assets/Images.xcassets/Composer/Underlined.imageset/Contents.json new file mode 100644 index 000000000..c394abde4 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Composer/Underlined.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Underlined.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Underlined@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Underlined@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Composer/Underlined.imageset/Underlined.png b/Riot/Assets/Images.xcassets/Composer/Underlined.imageset/Underlined.png new file mode 100644 index 0000000000000000000000000000000000000000..ec6995c2c82b4f5cfeb925c2933319d597ff3edb GIT binary patch literal 410 zcmV;L0cHM)P)Mf+t=E{u41E2KLW@Hm16EU9YSYO|ex23gc`zpOtItB(_qi zFAcZ}wrQX|n@`}}^gBTr9hqP?zL+a?ex{$kT`7NE9k98q zQ-TzQH^-}ibONV$(*ySxLPO>yhob-Q);hyoNckfNivAaO~T6OR{v%|~O&#T*(G z*?J)(_Xv9ZQOFf;P!MuljII1)%)f?z?Uf}6g0LTa0d3k*(I4EphX4Qo07*qoM6N<$ Eg2dgSd^DaAxus!(idq=3u)~Qdx z&%uH*^>$O#`bCGB*tw!FiLSnu<{VXqYj=NyaT ze*Z3^UZclnE^mJRl+lSpu|)t#%xZrSVB=*i^?K#c8^+x81?%oezG;=zJNPszR7B=f z<=ZQJzP?Fk+qK{9!};s09+@z>T#1!<^>9+my3nJU4FBAP7)<7{Fz~7~Ivn<7xKIG+ zHaIUUoT%GpYu?LyMP$F1_~ z=B`H7n_)9D+wSPBb6yUTX?m%nDS7Y0<*yRnmu&v$r6*Q6%{m5@2)<$^v-fw$rf;Ib zi`LcKOSC*(l5@|-d)eC?DHrn+wH_b+_xCM_97}QZ+to9Ac6?2Fe^0&Q z(NeyOJNswPkgsddJ~gr3&OE>JWJB?1pA#R~6j=Y^{oE3*b?1G4Z9{sTw&QHht|;>v zKYe>M*T0<;nse{dEJpF8i@(dN)kYiV3oWiz)HY(fecdAC{3QFiLQWhHng07#x9F4wvm2~W`uu#3+~hna2NMTR t<0F>AinBibIHkG`6p6r?>{#}j)BNK0WY?<59AGkH@O1TaS?83{1OO-!9D@J= literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/Underlined.imageset/Underlined@3x.png b/Riot/Assets/Images.xcassets/Composer/Underlined.imageset/Underlined@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..b467bdc1715ab51768a503baf1e2b70cbe8948be GIT binary patch literal 980 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGooCO|{#S9FJK(?X*8o|0J?9FfeC&x;TbZFuuKg&`;P=g8f7OGS-LJ%s1FE zequW<+~MvVQf0VOE@N@g(m6gyZb>kk#zY-`C0Nt+jrV~12D`?+jF;w~m(694%zkBm$etIX+I%JTbCBnJFRLatBW^^*2_mYrv|5hcR$l0f3_vXTs zoW1wCOTzMR$TR=Gsv5>#^{-@ejK&v_32KbbQp)C^3}Ku5o@G`^_3C}%7esih_WO#@ zegDL>@Z?)|&d+iO7EEq_eJh7U+Jv=P`M=e{El%>&{~I5Ox&MCxV-@cK7ZMd^>B0chesB1(sF~(OGqk zLMi9cl$i~pNjBq zjcJ?H+Krtz+q-QwzI?HJxpudT#}lP9F87sY6+inCrsOT+Ja1*dle!P?LTbVdkJ6s7 zb$Oe|ZjQX0w^nPZoO|x%(3#9aYC;DRB&*D`q5K0><}vo!a)-U$&i3=boy%IAXS8knKdD0Z1Rtxf`JM^v3yq$dl9{8irZYSZeMn}3}Y*h?r-Ep4O5Ol2U zP@uhY>sR%E`8W2k6#Ofglg_8UYvCvTjYkADeLaCVWi;FtZnDgsVF7z6_w-mNkE8>FPvc%aC`=lKP_kl^Q=<=(tH zC2~F|H#8;Ox6^tq%liUxZR2CLjI4&Na={;>eHxF82wa>l>wn#vLo9T*5zuA^Pgg&e IbxsLQ0NiXw@&Et; literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/bullet_list.imageset/Bullet list@2x.png b/Riot/Assets/Images.xcassets/Composer/bullet_list.imageset/Bullet list@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8e6e0f9e9d17a61523e22433e3996fc96d05d48b GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9E$svykh8Km+7D9BhG zz1JD#oktDF(GpR;)G ztlx`*t_BDE5d8CR?i97`>-WDp>^gt@H$TIHQUz{}1|0{n3=qM<@ZiuyRuKm=1#S}% e0deI6AzeoI-fi!53a$5mL_A&nT-G@yGywnwI){M( literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/bullet_list.imageset/Bullet list@3x.png b/Riot/Assets/Images.xcassets/Composer/bullet_list.imageset/Bullet list@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..356357067b0c70f4599ed53692d3afdab684c8f3 GIT binary patch literal 586 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGooCO|{#S9FJK(?X*8o|0J>kz_PC@HcopR z^E{o)|LF*<+4ya_%c3$>-f4Rp?U?)zo!kFKuH2(V>ef+NW6AsHvy6<*eV^UCSDaR- z1T-E28W!}Zr2O6xsuy8>P0(B`?AGhgSGPTw{XjS%-^}}6;fqW1)As+Hv~$zZ<+r@n zmg)a6P-r}A_ow64lKo+y+=UAtf0GiIUNtw>K2Y_fQ0V@IHz!{my3(bh@WMV}_eHJf zjo0_zeSE~XB;Wn(eMKGd$6BbQfOn{#E!3>aMjCWRFdwUeD$X!N8@w6@Wv0h z8G9pNiLig~{mB1$@^+V;A4{F2e~CiO0+|N}3h{A_GR$^+MNEspmUz1QxvXN literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/bullet_list.imageset/Contents.json b/Riot/Assets/Images.xcassets/Composer/bullet_list.imageset/Contents.json new file mode 100644 index 000000000..7fe1ca95e --- /dev/null +++ b/Riot/Assets/Images.xcassets/Composer/bullet_list.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Bullet list.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Bullet list@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Bullet list@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Composer/indent_decrease.imageset/Contents.json b/Riot/Assets/Images.xcassets/Composer/indent_decrease.imageset/Contents.json new file mode 100644 index 000000000..73db7a0bf --- /dev/null +++ b/Riot/Assets/Images.xcassets/Composer/indent_decrease.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Indent decrease.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Indent decrease@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Indent decrease@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Composer/indent_decrease.imageset/Indent decrease.png b/Riot/Assets/Images.xcassets/Composer/indent_decrease.imageset/Indent decrease.png new file mode 100644 index 0000000000000000000000000000000000000000..bba698715a04a398cc01a11300d2862db26dee69 GIT binary patch literal 274 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG zO zJuG19{oP)RmM!z%!2a#1oAeCjmY#Jd`X46F-ajjS^FHRcPfSGrv3F?fv{z)XdBlBT S#WA3h89ZJ6T-G@yGywoaz-G1p literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/indent_decrease.imageset/Indent decrease@2x.png b/Riot/Assets/Images.xcassets/Composer/indent_decrease.imageset/Indent decrease@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..12d52fb693a5430db49aab1d81b2d216578c945d GIT binary patch literal 449 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9E$svykh8Km+7D9BhG z^q$7b#y7jF1(qmmGiKg>yJo+$v=iS<&lxqG-Yh_af#7j+ z$I5%}(&`F$C5zoMOP3jatE>Cy#v^K2`8=2HsQcvtJzm3&(hm2Y2mI-uH{%Dd!85%- zWm9H-|LbsAnUTR@Lvh2Dq&|ctkTfHExPv9BD zRmp&S`>=bK(%YwZpHBJwR3fYV!23tui!L<;b@w0P)!6wX!-;9qqwe#Jub+Q&D|&kR zJfrZ#;yqtvHq41P?UVU$`zNI6z~yIm!$Q`6``)j{Zlkkq?F&B-A651#cUZQnL}UrO zSSG~h9ICop_O5|*zBiwT*$St{c8BjzDfyE4VxBPbKK1~unNJuPB+S?FE2ypEKk%zT m=NkKu0>yn3m?7ctz~X+yrei^8UZy?<33$5txvX*8o|0J>kx$)d4b$RRo!MDF@y)`!7W)KPwOA#5=eu9qIZfqA)b00k=6_p~GE?J2{Wi7E zlbhCPa)od;JSwSbC`|V3DgLCvb%pKW>)khd1&gO0QkWATFt_UG_j(3~hPzA6c(01- zWUopRTcYcBEo7yRM|97cP92TFt8W+uH!VAr>iC*xxxa5=(C6z1UZ?(Dp)buf%~Uhp zF_6n(&v#>q?CSgTZ-1S9>fEtyqJPA`?EY9Ddx7b~kIzk_4aeejJQy?nJiWs0@Ob?; z^)3@>`>8q}YuPi*>gKUpemu_iai8UX-p5hRwop9=t&WZWQLm#_5{o7o{_V{>noLXf0)$$Wf zBAMS>L|7zSG!GRWOmh@T+_>O!K$n63k75SbAD*V%QEK{4^JRak*A%~alK(mT8msG( zT?2UxPfceJw3s!H$zu0ex4@gOI>`dCfpy`IbhS(+rSfl{y aRWF5;y^PDYG{|fRiF&&FxvX``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{#8#G$B+ufxf3t)HW>)Cy_b4qtZc#5X(uV1 z$aI(C?g8Esmb(q2Ga3X$;~iuV@;^xVbqN3f literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/maximise_composer.imageset/maximise_composer@2x.png b/Riot/Assets/Images.xcassets/Composer/maximise_composer.imageset/maximise_composer@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..02bfab959d04c2edef3b3c2a48e3a97aeaffd6cf GIT binary patch literal 450 zcmV;z0X_bSP)#{QZCbarTRR=6zhvc| zFOICS0rnIH0=a*B6yGZdVSt-s2qT0uCe>x)IKa z2I1VO7w!Y~!hN8erAdO9poA8=QBG629$(&Z>FlgHxND^O$HDdr7ZdNahzcI)n@ZX( sJ(nwLM`#x=T$soWJc1s@6Z8lkg&GqV8dHzJ5!g1ZAfGHm0%c%k-aP+{ zCT*wiE1~T$CHki+VlK@jd;&-g9gxfSym72aiZXZXr)uC zs=Pf`4E!8Az24;A@gm?;(I_~44jKi8&qbqPa2qrV0=GpYcDM^PVuiaxBR04-T2)2+ zE(_erk#0|kD=NHCvRs}XKik8U_;Ts@(#Atn&gf5PW$%{jwQU6c=L5(hx zg+!xSbCu9&G#M@djV8gRpph|L5*iu7rJ>O%Tp}8sg-b=FLAYcz>W441Xf#^(Jy^e+ zZD-yn3XM#@Rm7pua^(4<{wLs(s#8)=d6SW`l-^wJh^aAnthIVFz210!{g^Z*w#oH^ rknxn*wgz6(j$Bl)Fwxh_`_V?<97i&DKdTNK00000NkvXXu0mjfG$!t| literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/minimise_composer.imageset/Contents.json b/Riot/Assets/Images.xcassets/Composer/minimise_composer.imageset/Contents.json new file mode 100644 index 000000000..e67839028 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Composer/minimise_composer.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "minimise_composer.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "minimise_composer@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "minimise_composer@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Composer/minimise_composer.imageset/minimise_composer.png b/Riot/Assets/Images.xcassets/Composer/minimise_composer.imageset/minimise_composer.png new file mode 100644 index 0000000000000000000000000000000000000000..13c74aa38414eaf75f2801b90bda6d2f883970ac GIT binary patch literal 348 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|&H|6fVg?3oVGw3ym^DWND9BhG z|kNnR|KBO~p=6YYN$M>2ZYeO#s;5i|5onQHNjt5SjUuIS!- q{g;ZjvCsCLrec1pzRu5e2Q%~P#i>U_{W^f2XYh3Ob6Mw<&;$S~e1((% literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/minimise_composer.imageset/minimise_composer@2x.png b/Riot/Assets/Images.xcassets/Composer/minimise_composer.imageset/minimise_composer@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e2d4ab52295dd0b0e3f413dd6496141e8cf846e6 GIT binary patch literal 488 zcmVP)F93SQ2AQ4}jzOt1@N_nH7Z>T2UB(ZRjDAxlQTH@~pp{oS1NDJE}MiH@* z7+l0wVo(tmiNQo%B?$Ipm16HwOH5rP%5}gFI!h32$$Izrg4JVvaEqwk8W+?Oc(9?T zzu{a6Id3{&PhhsvD8Os$i*?yR;i~$Uu{1Qm*0TewK3J?~=f^Sh!2Co6Osyr#>oJ0! znD@w6uvGg+u5WN?I!!IQ=?*5GyXIe5RhXTf^y)yT&00@PYEfXqTErCFtvNA-NSE2c zE;5G1R*|tJT1Cc`XcQS+!YJY-K{Ml*40;lDmLS#%jF>@BL}v+RN)h^NK>s5D@FhW1 e_ekbTg7^VNF>H@zxNq$M0000UWzo80N#nTkpNzaw3Ptfh?qzKFGNfwK<^?p zBtWkswj@B`BVt7YI89N?Fvd=qB?-`*2(5Ci60IeGFVnA)1aO+hKRiB*$^(ME%I5`X zz(^ETB|x8{wSkGT?y2aBsuI9$XbHVuS5i>|bSff4X)AO&QRs*T8r2$ifqtI z#5W1hSj0yO&`88r37{?Fvjor*@m&HaMM97OwIZQNfW0CiNq|ZbTM|1ZfL5Ef{7K`j zj>K;Xz+}pMEVwLlJ7g=Z4lSI@tV3({2bCZtKsmG$`SbBDQFszybF&|OLLey}3q}Hb Y19&hw#sLRPmH+?%07*qoM6N<$f+oxPcmMzZ literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Composer/start_compose_module.imageset/Contents.json b/Riot/Assets/Images.xcassets/Composer/start_compose_module.imageset/Contents.json new file mode 100644 index 000000000..397c5d0dc --- /dev/null +++ b/Riot/Assets/Images.xcassets/Composer/start_compose_module.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "start_compose_module.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "start_compose_module@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "start_compose_module@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/Composer/start_compose_module.imageset/start_compose_module.png b/Riot/Assets/Images.xcassets/Composer/start_compose_module.imageset/start_compose_module.png new file mode 100644 index 0000000000000000000000000000000000000000..c30133af2e377915c0b018dedcf917843b01718d GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{!UL9$B+ufw^KLr9x@Pc-S68Hlh$IuHCbT# zlqo(AqA`uGPq`$T15dCm=~;B*f%BF|?-VYVxYDEFr9uaY*|{55F${Cge+0a~v96~~w*dh2ieuskmPdQ=+7Gn~C=%``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBIZjU($B+ufx7Q8*4m$|6KHQ$&T|7hhfZziO z5s@kDI6Oo`TDq7t{nA%x2%IuJAvj6+$n%bC#v651ix!CleslGj^7EADoA^!FZZZ0` zFyDOe`hB45@3)6zjcU%lzvt+{t9xp`Pl4gWAFkl^*i$YyN(Tx^648 zQ`p*vQ)Bic#kG9Ffqk1ACMY(Am*0JJ;^-}InX0X)L!{ zxA142$z}FJ>o0#rbuJbt7{+bSam#)$A!fcoT zV)Bel`WA_;ojp}wt;fiA>6OTn6s|kLc|_uSyU=cxZ2FwH}i|L%i=%D1xBw`o~@4l`s>!$ zWP_#rk1MML4xi+lc)R`i!EgJ;Mfo&8>Yw*tzQHA~^&oDy!DX)We3~V{;e;oH?D5Z= wW*R-?1$t7T?cb~OdPUn;b2tAJ602o4Uz&T~tMRlHFq#-VUHx3vIVCg!0J%KLQUCw| literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter.imageset/Contents.json b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter.imageset/Contents.json new file mode 100644 index 000000000..81ee52eeb --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "user_other_sessions_filter.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter.imageset/user_other_sessions_filter.svg b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter.imageset/user_other_sessions_filter.svg new file mode 100644 index 000000000..a2b8549a1 --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter.imageset/user_other_sessions_filter.svg @@ -0,0 +1,3 @@ + + + diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter_selected.imageset/Contents.json b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter_selected.imageset/Contents.json new file mode 100644 index 000000000..89113e4ef --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter_selected.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "user_other_sessions_filter_selected.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter_selected.imageset/user_other_sessions_filter_selected.svg b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter_selected.imageset/user_other_sessions_filter_selected.svg new file mode 100644 index 000000000..f964fdd1c --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_filter_selected.imageset/user_other_sessions_filter_selected.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_inactive.imageset/Contents.json b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_inactive.imageset/Contents.json new file mode 100644 index 000000000..f16dd6afe --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_inactive.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "user_other_sessions_inactive.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_inactive.imageset/user_other_sessions_inactive.svg b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_inactive.imageset/user_other_sessions_inactive.svg new file mode 100644 index 000000000..8603bbc31 --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_inactive.imageset/user_other_sessions_inactive.svg @@ -0,0 +1,3 @@ + + + diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_unverified.imageset/Contents.json b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_unverified.imageset/Contents.json new file mode 100644 index 000000000..64debb2e6 --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_unverified.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "user_other_sessions_unverified.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_unverified.imageset/user_other_sessions_unverified.svg b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_unverified.imageset/user_other_sessions_unverified.svg new file mode 100644 index 000000000..738e3ed9c --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_unverified.imageset/user_other_sessions_unverified.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_verified.imageset/Contents.json b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_verified.imageset/Contents.json new file mode 100644 index 000000000..fd25f3b8e --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_verified.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "user_other_sessions_verified.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_verified.imageset/user_other_sessions_verified.svg b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_verified.imageset/user_other_sessions_verified.svg new file mode 100644 index 000000000..793d65784 --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_other_sessions_verified.imageset/user_other_sessions_verified.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_session_list_item_inactive_session.imageset/Contents.json b/Riot/Assets/Images.xcassets/DeviceManager/user_session_list_item_inactive_session.imageset/Contents.json new file mode 100644 index 000000000..e3af9f053 --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_session_list_item_inactive_session.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "user_session_list_item_inactive_session.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_session_list_item_inactive_session.imageset/user_session_list_item_inactive_session.svg b/Riot/Assets/Images.xcassets/DeviceManager/user_session_list_item_inactive_session.imageset/user_session_list_item_inactive_session.svg new file mode 100644 index 000000000..5aba5a38b --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_session_list_item_inactive_session.imageset/user_session_list_item_inactive_session.svg @@ -0,0 +1,3 @@ + + + diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_session_verification_unknown.imageset/Contents.json b/Riot/Assets/Images.xcassets/DeviceManager/user_session_verification_unknown.imageset/Contents.json new file mode 100644 index 000000000..7c84236f4 --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_session_verification_unknown.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "user_session_verification_unknown.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/DeviceManager/user_session_verification_unknown.imageset/user_session_verification_unknown.svg b/Riot/Assets/Images.xcassets/DeviceManager/user_session_verification_unknown.imageset/user_session_verification_unknown.svg new file mode 100644 index 000000000..3210e4185 --- /dev/null +++ b/Riot/Assets/Images.xcassets/DeviceManager/user_session_verification_unknown.imageset/user_session_verification_unknown.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_live.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_live.imageset/Contents.json new file mode 100644 index 000000000..062d578a0 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_live.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "action_live.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_live.imageset/action_live.svg b/Riot/Assets/Images.xcassets/Room/Actions/action_live.imageset/action_live.svg new file mode 100644 index 000000000..8b62b05b6 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Actions/action_live.imageset/action_live.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Riot/Assets/bg.lproj/InfoPlist.strings b/Riot/Assets/bg.lproj/InfoPlist.strings index 4e548cf76..1da155eba 100644 --- a/Riot/Assets/bg.lproj/InfoPlist.strings +++ b/Riot/Assets/bg.lproj/InfoPlist.strings @@ -1,7 +1,9 @@ // Permissions usage explanations -"NSCameraUsageDescription" = "Камерата се използва, за да се правят снимки и видеа, както и да се водят видео разговори."; -"NSPhotoLibraryUsageDescription" = "Галерията се използва, за да се изпращат снимки и видеа."; -"NSMicrophoneUsageDescription" = "Микрофонът се използва, за да се правят видеа и да се водят разговори."; -"NSContactsUsageDescription" = "За да открие контакти използващи Matrix, Element може да изпрати имейл адресите и телефонните номера от телефонния указател към избрания от вас Matrix сървър за самоличност. Ако се поддържа, личните данни могат да бъдат хеширани преди изпращане - вижте политиката за поверителност на сървъра за самоличност за повече информация."; +"NSCameraUsageDescription" = "Камерата се използва, за да се водят видео разговори, както и да се правят и изпращат снимки и видеа."; +"NSPhotoLibraryUsageDescription" = "Разрешете достъп до снимките, за да можете да качвате снимки и видеа от галерията си."; +"NSMicrophoneUsageDescription" = "Element се нуждае от достъп до микрофона за да прави и приема обаждания, да снима видеа и да записва гласови съобщения."; +"NSContactsUsageDescription" = "Ще бъдат споделени със сървъра ви за самоличност за да ви помогне да откриете контактите си в Matrix."; "NSCalendarsUsageDescription" = "Вижте насрочените срещи в приложението."; "NSFaceIDUsageDescription" = "Използва се Face ID за достъп до приложението."; +"NSLocationAlwaysAndWhenInUseUsageDescription" = "Когато споделяте местоположението си с хората, Element се нуждае от достъп за да им покаже карта."; +"NSLocationWhenInUseUsageDescription" = "Когато споделяте местоположението си с хората, Element се нуждае от достъп за да им покаже карта."; diff --git a/Riot/Assets/bg.lproj/Localizable.strings b/Riot/Assets/bg.lproj/Localizable.strings index 26cf616f0..1d4b738f2 100644 --- a/Riot/Assets/bg.lproj/Localizable.strings +++ b/Riot/Assets/bg.lproj/Localizable.strings @@ -67,3 +67,54 @@ /* A user added a Jitsi call to a room */ "GROUP_CALL_STARTED" = "Беше стартиран групов разговор"; + +/* A user's membership has updated in an unknown way */ +"USER_MEMBERSHIP_UPDATED" = "%@ обнови профила си"; + +/* A user has change their avatar */ +"USER_UPDATED_AVATAR" = "%@ смени аватара си"; + +/* A user has change their name to a new name which we don't know */ +"GENERIC_USER_UPDATED_DISPLAYNAME" = "%@ смени името си"; + +/** Membership Updates **/ + +/* A user has change their name to a new name */ +"USER_UPDATED_DISPLAYNAME" = "%@ смени името си на %@"; + +/* A user has reacted to a message, but the reaction content is unknown */ +"GENERIC_REACTION_FROM_USER" = "%@ изпрати реакция"; + +/** Reactions **/ + +/* A user has reacted to a message, including the reaction e.g. "Alice reacted 👍". */ +"REACTION_FROM_USER" = "%@ реагира с %@"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ сподели местоположението си"; + +/* New file message from a specific person, not referencing a room. */ +"FILE_FROM_USER" = "%@ изпрати файл %@"; + +/* New voice message from a specific person, not referencing a room. */ +"VOICE_MESSAGE_FROM_USER" = "%@ изпрати гласово съобщение"; + +/* New audio message from a specific person, not referencing a room. */ +"AUDIO_FROM_USER" = "%@ изпрати аудио файл %@"; + +/* New video message from a specific person, not referencing a room. */ +"VIDEO_FROM_USER" = "%@ изпрати видео"; + +/** Media Messages **/ + +/* New image message from a specific person, not referencing a room. */ +"PICTURE_FROM_USER" = "%@ изпрати снимка"; + +/* New message reply from a specific person in a named room. */ +"REPLY_FROM_USER_IN_ROOM_TITLE" = "%@ отговори в %@"; + +/* New message reply from a specific person, not referencing a room. */ +"REPLY_FROM_USER_TITLE" = "%@ отговори"; +/** General **/ + +"Notification" = "Уведомление"; diff --git a/Riot/Assets/bg.lproj/Vector.strings b/Riot/Assets/bg.lproj/Vector.strings index 9c760b32a..a749304aa 100644 --- a/Riot/Assets/bg.lproj/Vector.strings +++ b/Riot/Assets/bg.lproj/Vector.strings @@ -1837,3 +1837,24 @@ "notice_declined_video_call" = "%@ отказа разговора"; "e2e_passphrase_too_short" = "Паролата е прекалено кратка (трябва да е дълга поне %d символа)"; "resume_call" = "Възобнови"; +"onboarding_splash_login_button_title" = "Вече имам акаунт"; + +// MARK: Onboarding +"onboarding_splash_register_button_title" = "Създай акаунт"; +"accessibility_button_label" = "бутон"; +"saving" = "Запазване"; + +// Activities +"loading" = "Зареждане"; +"invite_to" = "Покани в %@"; +"confirm" = "Потвърди"; +"edit" = "Редактирай"; +"suggest" = "Предложи"; +"add" = "Добави"; +"existing" = "Съществуващо"; +"new_word" = "Ново"; +"stop" = "Спри"; +"done" = "Готово"; +"open" = "Отвори"; +"joining" = "Присъединяване"; +"enable" = "Включи"; diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 1942aa352..6fc34bb32 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -13,7 +13,7 @@ "title_people" = "Personen"; "title_rooms" = "Räume"; "warning" = "Warnung"; -"remove" = "Entferne"; +"remove" = "Entfernen"; "start" = "Starte"; "create" = "Erstellen"; "on" = "An"; @@ -100,7 +100,7 @@ "search_messages" = "Nachrichten"; "search_people" = "Personen"; "search_files" = "Dateien"; -"search_default_placeholder" = "Suche"; +"search_default_placeholder" = "Suchen"; "search_people_placeholder" = "Suche nach Nutzer-ID, Name oder E-Mail"; "search_no_result" = "Keine Ergebnisse"; // Directory @@ -568,10 +568,10 @@ "settings_key_backup_info_trust_signature_invalid_device_verified" = "Sicherungskopie hat eine ungültige Signatur von %@"; "settings_key_backup_info_trust_signature_invalid_device_unverified" = "Sicherungskopie hat eine ungültige Signatur von %@"; "settings_key_backup_button_create" = "Beginne Wiederherstellung mit Hilfe der Sicherheitskopie"; -"settings_key_backup_button_restore" = "Wiederherstellung mit Hilfe der Sicherheitskopie"; -"settings_key_backup_button_delete" = "Sicherheitskopie löschen"; +"settings_key_backup_button_restore" = "Von Sicherung wiederherstellen"; +"settings_key_backup_button_delete" = "Lösche Sicherung"; "settings_key_backup_button_use" = "Benutze Schlüssel Sicherheitskopie"; -"settings_key_backup_delete_confirmation_prompt_title" = "Sicherheitskopie löschen"; +"settings_key_backup_delete_confirmation_prompt_title" = "Lösche Sicherung"; "settings_key_backup_delete_confirmation_prompt_msg" = "Bist du Sicher? Damit gehen alle verschlüsselten Mitteilungen verloren wenn deine Schlüssel nicht anderweitig richtig gespeichert wurden."; "room_does_not_exist" = "%@ existiert nicht"; "key_backup_setup_title" = "Sicherheitskopie des Schlüssels"; @@ -628,7 +628,7 @@ "key_backup_setup_banner_subtitle" = "Beginne Schlüsselsicherung zu nutzen"; "key_backup_recover_banner_title" = "Verliere niemals verschlüsselte Nachrichten"; "key_backup_recover_banner_subtitle" = "Benutze Schlüsselsicherung"; -"sign_out_existing_key_backup_alert_title" = "Bist du sicher, dass du dich abmelden willst?"; +"sign_out_existing_key_backup_alert_title" = "Bist du sicher, dass du dich abmelden möchtest?"; "sign_out_existing_key_backup_alert_sign_out_action" = "Abmelden"; "sign_out_non_existing_key_backup_alert_title" = "Du verlierst den Zugriff auf deine verschlüsselten Nachrichten, wenn du dich jetzt abmeldest"; "sign_out_non_existing_key_backup_alert_setup_key_backup_action" = "Beginne Schlüsselsicherung zu nutzen"; @@ -636,7 +636,7 @@ "sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Du wirst deine verschlüsselten Nachrichten verlieren"; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Du verlierst den Zugriff auf deine verschlüsselten Nachrichten, es sei denn, du sicherst deine Schlüssel, bevor du dich abmeldest."; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_sign_out_action" = "Abmelden"; -"sign_out_non_existing_key_backup_sign_out_confirmation_alert_backup_action" = "Sicherungskopie"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_backup_action" = "Sicherung"; "sign_out_key_backup_in_progress_alert_title" = "Schlüsselsicherung läuft. Wenn du dich jetzt abmeldest, verlierst du den Zugriff auf deine verschlüsselten Nachrichten."; "sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Ich brauche meine verschlüsselten Nachrichten nicht"; "sign_out_key_backup_in_progress_alert_cancel_action" = "Ich werde warten"; @@ -646,7 +646,7 @@ "e2e_key_backup_wrong_version_button_settings" = "Einstellungen"; "e2e_key_backup_wrong_version_button_wasme" = "Das war ich"; "key_backup_setup_intro_manual_export_info" = "(Erweitert)"; -"key_backup_setup_intro_manual_export_action" = "Manueller Schlüssel-Export"; +"key_backup_setup_intro_manual_export_action" = "Schlüssel manuell exportieren"; // String for App Store "store_short_description" = "Sicherer, dezentralisierter Chat/VoIP"; "store_full_description" = "Element ist die neue Art von Kommunikations- und Kooperations-App, die:\n\n1. dir die Kontrolle gibt, deine Privatsphäre zu schützen\n2. dir die Kommunikation mit anderen Personen im Matrix-Netzwerk und darüber hinaus Integration in Apps wie Slack ermöglicht\n3. dich vor Werbung, Datenerfassung, Hintertüren und geschlossene Plattformen schützt\n4. dich durch Ende-zu-Ende-Verschlüsselung absichert und mit Quersignaturen andere überprüft\n\nElement unterscheidet sich grundlegend von anderen Kommunikations- und Kooperations-Diensten, da es dezentralisiert und Open-Source ist.\n\nElement lässt dir die Wahl, ob du einen eigenen Server betreibst oder einen bestehenden wählst, sodass du Datenschutz, Eigentum und Kontrolle über deine Daten und Konversationen hast. Du erhältst Zugriff auf ein offenes Netzwerk und bist nicht auf Element-Nutzer beschränkt. Und es ist sehr sicher.\n\nElement ist in der Lage, all dies zu tun, da es mit Matrix arbeitet – dem Standard für offene, dezentrale Kommunikation.\n\nMit Element hast du die Kontrolle, indem du auswählen kannst, bei wem deine Unterhaltungen liegen. In der Element-App kannst du verschiedene Betreiber auswählen:\n\n1. Hole dir ein kostenloses Konto auf dem öffentlichen Server von matrix.org\n2. Beherberge dein Konto selbst, indem du einen Server auf deiner eigenen Hardware betreibst\n3. Registriere ein Konto auf einem maßgeschneiderten Server, indem du einfach die Element-Matrix-Services abonnierst\n\nWarum Element?\n\nBESITZE DEINE DATEN: Du entscheidest, wo deine Daten und Nachrichten aufbewahrt werden sollen. Du besitzt und kontrollierst sie, nicht irgendein MEGAKONZERN, der deine Daten verwertet oder dritten Zugriff gewährt.\n\nOFFENE KOMMUNIKATION UND ZUSAMMENARBEIT: Du kannst mit allen anderen Mitgliedern des Matrix-Netzwerks schreiben, unabhängig davon, ob sie Element oder eine andere Matrix-App verwenden, selbst wenn sie eine andere Plattform wie beispielsweise Slack, IRC oder XMPP verwenden.\n\nSUPER SICHER: Echte Ende-zu-Ende-Verschlüsselung (nur diejenigen in der Konversation können Nachrichten entschlüsseln) und Quersignierung, um die Geräte der Konversationsteilnehmer zu überprüfen.\n\nVOLLSTÄNDIGE KOMMUNIKATION: Schreiben, Sprach- und Videoanrufe, Dateifreigabe, Bildschirmfreigabe und eine ganze Reihe von Integrationen, Bots und Widgets. Erschaffe Räume, Gemeinschaften, bleib in Kontakt und erledige Dinge.\n\nÜBERALL, WO DU BIST: Bleibe mit dem vollständig synchronisierten Nachrichtenverlauf auf all deinen Geräten und im Internet (unter https://element.io/app) unabhängig voneinander in Kontakt."; @@ -657,9 +657,9 @@ "room_event_action_edit" = "Bearbeiten"; "room_action_reply" = "Antworten"; "settings_labs_message_reaction" = "Mit einem Emoji reagieren"; -"settings_key_backup_button_connect" = "Verbinde diese Sitzung mit der Schlüsselsicherung"; +"settings_key_backup_button_connect" = "Verbinde diese Sitzung mit einer Schlüsselsicherung"; "event_formatter_message_edited_mention" = "(bearbeitet)"; -"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Schlüssel dieses Geräts sichern"; +"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Verbinde dieses Gerät mit einer Schlüsselsicherung"; "key_backup_recover_connent_banner_subtitle" = "Schlüssel dieser Sitzung sichern"; // MARK: - Device Verification "device_verification_title" = "Sitzung verifizieren"; @@ -945,9 +945,9 @@ "key_verification_tile_request_incoming_title" = "Verifizierungsanfrage"; "key_verification_tile_request_outgoing_title" = "Verifizierung gesendet"; "key_verification_tile_request_status_data_loading" = "Daten laden…"; -"key_verification_tile_request_status_waiting" = "Warten…"; +"key_verification_tile_request_status_waiting" = "Warten …"; "key_verification_tile_request_status_expired" = "Abgelaufen"; -"key_verification_tile_request_status_cancelled_by_me" = "Du hast abgebrochen"; +"key_verification_tile_request_status_cancelled_by_me" = "Du brachst ab"; "key_verification_tile_request_status_cancelled" = "%@ hat abgebrochen"; "key_verification_tile_request_status_accepted" = "Du hast akzeptiert"; "key_verification_tile_request_incoming_approval_accept" = "Annehmen"; @@ -958,7 +958,7 @@ "user_verification_start_verify_action" = "Verifizierung starten"; "user_verification_start_information_part1" = "Für zusätzliche Sicherheit verifizieren "; "user_verification_start_information_part2" = " indem ein einmaliger Code auf beiden Geräten überprüft wird."; -"user_verification_start_waiting_partner" = "Warte auf %@…"; +"user_verification_start_waiting_partner" = "Warte auf %@ …"; "user_verification_start_additional_information" = "Um sicher zu sein, tut dies persönlich oder verwendet einen anderen Kommunikationsweg."; "user_verification_sessions_list_user_trust_level_trusted_title" = "Vertraut"; "user_verification_sessions_list_user_trust_level_warning_title" = "Warnung"; @@ -974,7 +974,7 @@ "user_verification_session_details_information_trusted_other_user_part2" = " verifiziert:"; "user_verification_session_details_information_untrusted_current_user" = "Verifiziere diese Sitzung, um sie als vertrauenswürdig zu markieren, und gewähren ihr Zugriff auf verschlüsselte Nachrichten:"; "user_verification_session_details_information_untrusted_other_user" = " hat sich in einer neuen Sitzung angemeldet:"; -"user_verification_session_details_additional_information_untrusted_other_user" = "Bis dieser Benutzer diese Sitzung vertraut, werden an und von ihm gesendete Nachrichten mit Warnungen gekennzeichnet. Alternativ kannst du dies manuell überprüfen."; +"user_verification_session_details_additional_information_untrusted_other_user" = "Bis dieser Benutzer dieser Sitzung vertraut, werden an und von ihm gesendete Nachrichten mit Warnungen gekennzeichnet. Alternativ kannst du dies manuell überprüfen."; "user_verification_session_details_additional_information_untrusted_current_user" = "Wenn du dich nicht zu dieser Sitzung angemeldet hast, ist dein Konto möglicherweise gefährdet."; "user_verification_session_details_verify_action_current_user" = "Interaktiv überprüfen"; "user_verification_session_details_verify_action_other_user" = "Manuell Verifizieren"; @@ -986,8 +986,8 @@ "device_verification_self_verify_alert_cancel_action" = "Das war ich nicht"; "device_verification_self_verify_start_verify_action" = "Überprüfung starten"; "device_verification_self_verify_start_information" = "Benutze diese Sitzung um deine Neue zu verifizieren. Erlaube Zugriff auf die verschlüsselten Nachrichten."; -"device_verification_self_verify_start_waiting" = "Warte…"; -"device_verification_self_verify_wait_title" = "vervollständige Sicherheit"; +"device_verification_self_verify_start_waiting" = "Warten …"; +"device_verification_self_verify_wait_title" = "Sicherheit vervollständigen"; "device_verification_self_verify_wait_information" = "Überprüfe diese Sitzung von einer anderen aus, um Zugriff auf die verschlüsselten Nachrichten zu erhalten.\n\nBenutze die neuest %@-Sitzung auf deinem anderen Gerät:"; "device_verification_self_verify_wait_waiting" = "warte…"; "skip" = "Überspringen"; @@ -1000,14 +1000,14 @@ "room_member_power_level_short_custom" = "Selbstdefiniert"; "security_settings_secure_backup" = "SICHERE SICHERHEITSKOPIE"; "security_settings_secure_backup_synchronise" = "Synchronisiere"; -"security_settings_secure_backup_delete" = "Backup löschen"; +"security_settings_secure_backup_delete" = "Lösche Sicherung"; "security_settings_crosssigning_info_ok" = "Quersignierung ist bereit zur Anwendung."; "security_settings_crosssigning_reset" = "Zurücksetzen"; "security_settings_coming_soon" = "Entschuldigung, diese Funktion ist noch nicht für %@ iOS verfügbar. Bitte nutze eine andere Matrix-Anwendung, um es einzurichten. %@ iOS wird es benutzen."; "security_settings_user_password_description" = "Bestätige deine Identität durch Eingabe des Passworts deines Matrix-Kontos"; // AuthenticatedSessionViewControllerFactory "authenticated_session_flow_not_supported" = "Diese App unterstützt nicht diese Authentifizierungsmethode für deinen Heimserver."; -"secure_key_backup_setup_intro_title" = "Sichere Datensicherung"; +"secure_key_backup_setup_intro_title" = "Verschlüsselte Sicherung"; "store_promotional_text" = "Privatsphäre-wahrende Kollaborations-App in einem offenen Netzwerk. Dezentral, um dir die Kontrolle zu geben. Keine Datenerfassung, keine Hintertüren und kein Zugriff durch Dritte."; "room_participants_action_security_status_complete_security" = "Vollständige Sicherheit"; "external_link_confirmation_title" = "Überprüfe diesen Link genau"; @@ -1024,17 +1024,17 @@ "event_formatter_widget_removed_by_you" = "Du hast das Widget entfernt: %@"; "event_formatter_jitsi_widget_added_by_you" = "Du hast eine VoIP-Konferenz hinzugefügt"; "event_formatter_jitsi_widget_removed_by_you" = "Du hast eine VoIP-Konferenz entfernt"; -"secure_key_backup_setup_intro_info" = "Absicherung um den Zugriffsverlust auf verschlüsselte Nachrichten und Daten zu verhindern, indem die Schlüssel für die Entschlüsselung auf dem Server gesichert werden."; +"secure_key_backup_setup_intro_info" = "Verhindere, den Zugriff auf verschlüsselte Nachrichten und Daten zu verlieren, indem du die Verschlüsselungs-Schlüssel auf deinem Server sicherst."; "secure_key_backup_setup_intro_use_security_key_title" = "Benutze einen Sicherheitsschlüssel"; -"secure_key_backup_setup_intro_use_security_key_info" = "Generiere einen Sicherheitsschlüssel, welcher z.B. in einer Passwortverwaltung oder in einem Tresor sicher aufbewahrt werden sollte."; +"secure_key_backup_setup_intro_use_security_key_info" = "Generiere einen Sicherheitsschlüssel, den du in einem Passwort-Manager oder Tresor sicher aufbewahren solltest."; "secure_key_backup_setup_intro_use_security_passphrase_title" = "Benutze Sicherungsphrase"; "secure_key_backup_setup_intro_use_security_passphrase_info" = "Gib eine geheime Phrase ein, die nur du kennst, um einen Schlüssel für die Sicherung zu generieren."; "secure_key_backup_setup_existing_backup_error_title" = "Eine Sicherheitskopie für Nachrichten existiert bereits"; "secure_key_backup_setup_existing_backup_error_info" = "Entsperre es, um es in der sicheren Datensicherung wiederzuverwenden, oder lösche es, um eine neue Nachrichtensicherung zu erstellen."; "secure_key_backup_setup_existing_backup_error_unlock_it" = "Entschlüsseln"; -"secure_key_backup_setup_cancel_alert_title" = "Sicher?"; -"secure_key_backup_setup_cancel_alert_message" = "Wenn du jetzt abbrichst und den Zugriff zu deinen Sitzungen verlierst, kannst du verschlüsselte Nachrichten & Daten verlieren.\n\nDu kannst auch eine Sicherung einrichten und deine Schlüssel in den Einstellungen verwalten."; -"secure_backup_setup_banner_title" = "Sichere Datensicherung"; +"secure_key_backup_setup_cancel_alert_title" = "Bist du sicher?"; +"secure_key_backup_setup_cancel_alert_message" = "Wenn du jetzt abbrichst und den Zugriff zu deinen Sitzungen verlierst, kannst du verschlüsselte Nachrichten und Daten verlieren.\n\nDu kannst auch eine Sicherung einrichten und deine Schlüssel in den Einstellungen verwalten."; +"secure_backup_setup_banner_title" = "Verschlüsselte Sicherung"; "secure_backup_setup_banner_subtitle" = "Absicherung gegen den Zugriffsverlust auf verschlüsselte Nachrichten und Daten"; // Recover from private key "key_backup_recover_from_private_key_info" = "Sicherung wird wiederhergestellt…"; @@ -1083,8 +1083,8 @@ "key_verification_verify_qr_code_other_scan_my_code_title" = "Hat dein Gegenüber den QR-Code erfolgreich gescannt?"; "key_verification_verify_qr_code_scan_other_code_success_title" = "Code erfolgreich überprüft!"; // Scanning -"key_verification_scan_confirmation_scanning_title" = "Fast da! Warten auf Bestätigung…"; -"key_verification_scan_confirmation_scanning_user_waiting_other" = "Warten auf %@…"; +"key_verification_scan_confirmation_scanning_title" = "Fast geschafft! Warte auf Bestätigung …"; +"key_verification_scan_confirmation_scanning_user_waiting_other" = "Warte auf %@ …"; "key_verification_scan_confirmation_scanning_device_waiting_other" = "Warte auf das andere Gerät…"; // Scanned "key_verification_scan_confirmation_scanned_title" = "Fast da!"; @@ -1166,7 +1166,7 @@ "biometrics_cant_unlocked_alert_message_x" = "Zum Entsperren nutze %@ oder melde dich erneut an und reaktiviere %@"; "biometrics_cant_unlocked_alert_message_login" = "Erneut anmelden"; "biometrics_cant_unlocked_alert_message_retry" = "Erneut probieren"; -"device_verification_self_verify_wait_recover_secrets_checking_availability" = "Nach anderen Überprüfungsfunktionen suchen ..."; +"device_verification_self_verify_wait_recover_secrets_checking_availability" = "Nach anderen Überprüfungsfunktionen suchen …"; "joined" = "Beigetreten"; "switch" = "Ändern"; "more" = "Mehr"; @@ -1178,14 +1178,14 @@ "searchable_directory_x_network" = "%@ Netzwerk"; "searchable_directory_search_placeholder" = "Name oder ID"; "create_room_title" = "Neuer Raum"; -"create_room_section_header_name" = "NAME"; +"create_room_section_header_name" = "Name"; "create_room_placeholder_name" = "Name"; "create_room_section_header_topic" = "THEMA (OPTIONAL)"; "create_room_placeholder_topic" = "Um was geht es in diesem Raum?"; "create_room_section_header_encryption" = "VERSCHLÜSSELUNG"; "create_room_enable_encryption" = "Verschlüsselung aktivieren"; "create_room_section_footer_encryption" = "Verschlüsselung kann im Nachhinein nicht deaktiviert werden."; -"create_room_section_header_type" = "BEITRITTSBERECHTIGTE"; +"create_room_section_header_type" = "Beitrittsberechtigte"; "create_room_type_private" = "Privater Raum (nur Eingeladene)"; "create_room_type_public" = "Öffentlicher Raum (jeder hat Zugriff)"; "create_room_section_footer_type" = "Personen können einen privaten Raum nur mit Einladung betreten."; @@ -1366,7 +1366,7 @@ // Success from secure backup "key_backup_setup_success_from_secure_backup_info" = "Deine Schlüssel werden gesichert."; -"security_settings_secure_backup_restore" = "Von Backup wiederherstellen"; +"security_settings_secure_backup_restore" = "Von Sicherung wiederherstellen"; "security_settings_secure_backup_reset" = "Zurücksetzen"; "security_settings_secure_backup_info_valid" = "Diese Sitzung sichert deine Schlüssel."; "security_settings_secure_backup_info_checking" = "Überprüfen…"; @@ -1646,11 +1646,11 @@ // Login Screen "login_create_account" = "Konto erstellen:"; "login_server_url_placeholder" = "URL (z.B. https://matrix.org)"; -"login_home_server_title" = "Heimserver-URL:"; -"login_home_server_info" = "Dein Heimserver speichert alle deine Gespräche und Benutzerkontodaten"; -"login_identity_server_title" = "Identitätsserver-URL:"; -"login_identity_server_info" = "Matrix stellt Identitätsserver bereit, um feststellen zu können, welche E-Mail-Adressen, etc. zu welchen Matrix-IDs gehören. Momentan existiert nur https://vector.im."; -"login_user_id_placeholder" = "Matrix-ID (z.B. @bob:matrix.org oder bob)"; +"login_home_server_title" = "Heim-Server-Adresse:"; +"login_home_server_info" = "Dein Heim-Server speichert all deine Gespräche und Kontodaten"; +"login_identity_server_title" = "Identitätsserver-Adresse:"; +"login_identity_server_info" = "Matrix unterstützt Identitäts-Server, um zu ermitteln, welche E-Mail-Adressen etc. zu welchen Matrix-IDs gehören. Momentan existiert nur https://vector.im."; +"login_user_id_placeholder" = "Matrix-ID (z. B. @bob:matrix.org oder bob)"; "login_password_placeholder" = "Passwort"; "login_optional_field" = "optional"; "login_display_name_placeholder" = "Anzeigename (z.B. Peter Pan)"; @@ -1804,10 +1804,10 @@ "room_event_encryption_info_device_id" = "ID\n"; "room_event_encryption_info_device_verification" = "Überprüfung\n"; "room_event_encryption_info_device_fingerprint" = "Ed25519-Fingerabdruck\n"; -"room_event_encryption_info_device_verified" = "Überprüft"; +"room_event_encryption_info_device_verified" = "Verifiziert"; "room_event_encryption_info_device_not_verified" = "NICHT verifiziert"; "room_event_encryption_info_device_blocked" = "auf schwarzer Liste"; -"room_event_encryption_info_verify" = "Überprüfe..."; +"room_event_encryption_info_verify" = "Verifiziere …"; "room_event_encryption_info_unverify" = "Verifizierung widerrufen"; "room_event_encryption_info_block" = "Blockieren"; "room_event_encryption_info_unblock" = "Blockierung aufheben"; @@ -1839,7 +1839,7 @@ "room_creation_alias_placeholder" = "(z.B. #foo:example.org)"; "room_creation_alias_placeholder_with_homeserver" = "(z.B. #foo%@)"; "room_creation_participants_title" = "Teilnehmer:"; -"room_creation_participants_placeholder" = "(z.B. @laura:heimserver1; @thomas:heimserver2...)"; +"room_creation_participants_placeholder" = "(z. B. @laura:heimserver1; @thomas:heimserver2 …)"; // Room "room_please_select" = "Bitte wähle einen Raum"; "room_error_join_failed_title" = "Konnte Raum nicht betreten"; @@ -1873,14 +1873,14 @@ "attachment_multiselection_size_prompt" = "Bilder senden als:"; "attachment_multiselection_original" = "Originalgröße"; "attachment_e2e_keys_file_prompt" = "Diese Datei enthält von einer Matrix-Anwendung exportierte Schlüssel.\nMöchtest du den Dateiinhalt sehen oder die Schlüssel importieren?"; -"attachment_e2e_keys_import" = "Importiere..."; +"attachment_e2e_keys_import" = "Importiere …"; // Contacts "contact_mx_users" = "Matrixbenutzer"; "contact_local_contacts" = "Lokale Kontakte"; // Groups // Search "search_no_results" = "Nichts gefunden"; -"search_searching" = "Suche wird durchgeführt..."; +"search_searching" = "Suche wird durchgeführt …"; // Time "format_time_s" = "s"; "format_time_m" = "m"; @@ -1912,9 +1912,9 @@ "power_level" = "Berechtigungsstufe"; "network_error_not_reachable" = "Bitte Netzwerkverbindung prüfen"; "user_id_placeholder" = "z. B.: @thomas:heimserver"; -"ssl_homeserver_url" = "Heimserver URL: %@"; +"ssl_homeserver_url" = "Heim-Server-Adresse: %@"; // Permissions -"camera_access_not_granted_for_call" = "Video-Anrufe benötigen Zugriff auf die Kamera, aber %@ hat keine Berechtigung"; +"camera_access_not_granted_for_call" = "Videoanrufe benötigen Zugriff auf die Kamera, aber %@ hat keine Berechtigung"; "microphone_access_not_granted_for_call" = "Anrufe benötigen Zugriff auf das Mikrofon, aber %@ hat keine Berechtigung"; "local_contacts_access_not_granted" = "Finden von Benutzern in lokalen Kontakten benötigt Zugriff auf die Kontakte, aber %@ hat keine Berechtigung"; "local_contacts_access_discovery_warning_title" = "Benutzer finden"; @@ -2074,7 +2074,7 @@ "notification_settings_people_join_leave_rooms" = "Benachrichtige, wenn Benutzer einen Raum betreten oder verlassen"; "notification_settings_receive_a_call" = "Benachrichtige, wenn ich einen Anruf erhalte"; "notification_settings_suppress_from_bots" = "Unterdrücke Benachrichtigungen von Bots"; -"notification_settings_by_default" = "Als Standard..."; +"notification_settings_by_default" = "Standardmäßig …"; "notification_settings_notify_all_other" = "Benachrichtige für alle anderen Nachrichten/Räume"; // gcm section // Settings keys @@ -2166,9 +2166,9 @@ "authentication_verify_email_input_message" = "%@ muss deinen Account verifizieren"; "authentication_cancel_flow_confirmation_message" = "Dein Account ist noch nicht angelegt. Registrierung wirklich abbrechen?"; "authentication_server_selection_generic_error" = "Unter dieser URL konnte kein Server gefunden werden. Bitte überprüfe die Eingabe."; -"authentication_server_selection_register_message" = "Wie ist die Adresse deines Servers? Der Server ist wie ein Zuhause für all deine Daten"; -"authentication_server_info_title_login" = "Wo deine Unterhaltungen zum Leben erwachen"; -"authentication_server_info_title" = "Wo deine Unterhaltungen zum Leben erwachen"; +"authentication_server_selection_register_message" = "Wie lautet die Adresse deines Servers? Dies ist eine Art Zuhause für all deine Daten"; +"authentication_server_info_title_login" = "Der zukünftige Ort deiner Gespräche"; +"authentication_server_info_title" = "Der zukünftige Ort deiner Gespräche"; "authentication_registration_username_footer" = "Du kannst dies später nicht mehr ändern"; // MARK: Authentication @@ -2213,9 +2213,9 @@ "authentication_forgot_password_input_message" = "%@ wird dir einen Bestätigungslink senden"; "authentication_forgot_password_input_title" = "Gib deine E-Mail-Adresse ein"; "authentication_verify_email_waiting_button" = "E-mail erneut senden"; -"authentication_server_selection_server_url" = "Homeserver-URL"; -"authentication_server_selection_login_message" = "Wie ist die Adresse deines Servers?"; -"authentication_server_selection_register_title" = "Wähle deinen Homeserver aus"; +"authentication_server_selection_server_url" = "Heim-Server-Adresse"; +"authentication_server_selection_login_message" = "Wie lautet die Adresse deines Servers?"; +"authentication_server_selection_register_title" = "Wähle deinen Heim-Server"; "authentication_verify_email_text_field_placeholder" = "E-Mail-Adresse"; "authentication_forgot_password_waiting_button" = "E-Mail erneut senden"; "authentication_verify_email_input_title" = "Gib deine E-Mail-Adresse ein"; @@ -2232,14 +2232,14 @@ "authentication_login_username" = "Nutzername / E-Mail-Adresse / Telefonnummer"; "authentication_login_title" = "Willkommen zurück!"; "authentication_server_selection_login_title" = "Mit Homeserver verbinden"; -"location_sharing_invalid_power_level_message" = "Du brauchst die richtigen Berechtigungen, um deinen Live-Standort in diesem Raum zu teilen."; -"location_sharing_invalid_power_level_title" = "Du hast keine Berechtigung deinen Live-Standort zu teilen"; +"location_sharing_invalid_power_level_message" = "Du benötigst die entsprechenden Berechtigungen, um deinen Echtzeit-Standort in diesem Raum freizugeben."; +"location_sharing_invalid_power_level_title" = "Dir fehlt die Berechtigung, deinen Echtzeit-Standort freigeben zu dürfen"; "authentication_choose_password_not_verified_message" = "Überprüfe deinen Posteingang"; "authentication_choose_password_not_verified_title" = "E-Mail Adresse nicht bestätigt"; -"message_reply_to_sender_sent_their_live_location" = "Live-Standort."; -"location_sharing_live_lab_promotion_activation" = "Aktiviere Live-Standortfreigabe"; -"location_sharing_live_lab_promotion_text" = "Bitte beachte: Dies ist eine experimentelle Funktion. Sie benutzt eine temporäre Implementation und ermöglicht, dass andere Personen in diesem Raum den Verlauf deines geteilten Standortes permanent sehen können."; -"location_sharing_live_lab_promotion_title" = "Live-Standort-Freigabe"; +"message_reply_to_sender_sent_their_live_location" = "Echtzeit-Standort."; +"location_sharing_live_lab_promotion_activation" = "Aktiviere Echtzeit-Standortfreigabe"; +"location_sharing_live_lab_promotion_text" = "Bitte beachte: Dies ist eine experimentelle Funktion und temporäre Implementation, die es anderen Personen in diesem Raum dauerhaft ermöglicht, deinen Standortfreigabeverlauf sehen zu können."; +"location_sharing_live_lab_promotion_title" = "Echtzeit-Standortfreigabe"; "room_info_back_button_title" = "Raum-Info"; "network_offline_message" = "Du bist offline, überprüfe deine Internetverbindung."; "network_offline_title" = "Du bist offline"; @@ -2265,44 +2265,44 @@ "location_sharing_allow_background_location_cancel_action" = "Nicht jetzt"; "location_sharing_allow_background_location_validate_action" = "Einstellungen"; "location_sharing_allow_background_location_title" = "Zugriff erlauben"; -"settings_labs_enable_live_location_sharing" = "Teilen des Live-Standortes - teile deinen aktuellen Standort (aktive Entwicklung, temporäre Standorte bleiben im Verlauf des Raums)"; -"location_sharing_live_stop_sharing_progress" = "Standort-Freigabe beenden"; -"location_sharing_live_stop_sharing_error" = "Teilen des Live-Standortes konnte nicht gestoppt werden"; +"settings_labs_enable_live_location_sharing" = "Echtzeit-Standortfreigabe – teile deinen aktuellen Standort (Aktive in Entwicklung und temporär verbleiben Standorte im Raumverlauf)"; +"location_sharing_live_stop_sharing_progress" = "Beende Standortfreigabe"; +"location_sharing_live_stop_sharing_error" = "Beenden der Echtzeit-Standortfreigabe fehlgeschlagen"; "location_sharing_live_no_user_locations_error_title" = "Keine Standorte verfügbar"; "location_sharing_live_timer_selector_long" = "für 8 Stunden"; "location_sharing_live_timer_selector_medium" = "für 1 Stunde"; "location_sharing_live_timer_selector_short" = "für 15 Minuten"; "location_sharing_live_timer_selector_title" = "Lege fest, wie lange dein genauer Standort für andere sichtbar ist."; -"location_sharing_live_error" = "Live-Standort fehlgeschlagen"; -"location_sharing_live_loading" = "Lade Live-Standort..."; -"location_sharing_live_timer_incoming" = "Live bis %@"; -"live_location_sharing_ended" = "Live-Standort beendet"; -"location_sharing_live_list_item_stop_sharing_action" = "Stop"; +"location_sharing_live_error" = "Echtzeit-Standort-Fehler"; +"location_sharing_live_loading" = "Lade Echtzeit-Standort …"; +"location_sharing_live_timer_incoming" = "Echtzeit bis %@"; +"live_location_sharing_ended" = "Echtzeit-Standort beendet"; +"location_sharing_live_list_item_stop_sharing_action" = "Beenden"; "location_sharing_live_list_item_current_user_display_name" = "Du"; "location_sharing_live_list_item_last_update_invalid" = "Letzter Standort unbekannt"; "location_sharing_live_list_item_last_update" = "Vor %@ aktualisiert"; "location_sharing_live_list_item_sharing_expired" = "Freigabe abgelaufen"; -"location_sharing_live_list_item_time_left" = "%@ hat verlassen"; +"location_sharing_live_list_item_time_left" = "%@ übrig"; "location_sharing_live_viewer_title" = "Standort"; -"location_sharing_live_map_callout_title" = "Standort teilen"; +"location_sharing_live_map_callout_title" = "Standort freigeben"; "settings_presence_offline_mode_description" = "Wenn diese Option aktiviert ist, wirst Du anderen Nutzer:innen immer als offline angezeigt, auch wenn Du die Anwendung verwendest."; "settings_presence_offline_mode" = "Offline-Modus"; "settings_presence" = "Präsenz"; "threads_discourage_information_2" = "\n\nWillst du Threads trotzdem aktivieren?"; "threads_beta_cancel" = "Nicht jetzt"; "threads_beta_enable" = "Probiere es aus"; -"threads_beta_information_link" = "Mehr Informationen"; +"threads_beta_information_link" = "Mehr erfahren"; "threads_beta_information" = "Organisiere Diskussionen mit Threads.\n\nThreads helfen, Konversationen zu folgen und beim Thema zu bleiben. "; "threads_beta_title" = "Threads"; "ignore_user" = "Nutzer:in ignorieren"; "location_sharing_pin_drop_share_title" = "Teile diesen Standort"; "location_sharing_static_share_title" = "Meinen aktuellen Standort schicken"; -"live_location_sharing_banner_stop" = "Stop"; -"live_location_sharing_banner_title" = "Live-Standort aktiviert"; +"live_location_sharing_banner_stop" = "Beenden"; +"live_location_sharing_banner_title" = "Echtzeit-Standort aktiviert"; // MARK: Live location sharing -"location_sharing_live_share_title" = "Teile Live-Standort"; +"location_sharing_live_share_title" = "Echtzeit-Standort freigeben"; "side_menu_coach_message" = "Wische nach rechts oder tippe, um alle Räume zu sehen"; "spaces_add_room_missing_permission_message" = "Du hast keine Berechtigung, Räume zu diesem Space hinzuzufügen."; "spaces_creation_in_one_space" = "in 1 Space"; @@ -2310,7 +2310,7 @@ "spaces_creation_in_spacename_plus_many" = "in %@ + %@ Spaces"; "spaces_creation_in_spacename_plus_one" = "in %@ + 1 Space"; "spaces_creation_in_spacename" = "in %@"; -"spaces_creation_post_process_inviting_users" = "Lade %@ Nutzer:innen ein"; +"spaces_creation_post_process_inviting_users" = "Lade %@ Benutzer ein"; "spaces_creation_post_process_adding_rooms" = "Füge %@ Räume hinzu"; "spaces_creation_post_process_creating_room" = "Erstelle %@"; "spaces_creation_post_process_uploading_avatar" = "Lade Profilbild hoch"; @@ -2330,8 +2330,8 @@ "spaces_creation_email_invites_email_title" = "E-Mail"; "spaces_creation_email_invites_message" = "Du kannst sie auch später einladen."; "spaces_creation_email_invites_title" = "Lade dein Team ein"; -"spaces_creation_new_rooms_support" = "Support"; -"spaces_creation_new_rooms_random" = "Zufällig"; +"spaces_creation_new_rooms_support" = "Unterstützung"; +"spaces_creation_new_rooms_random" = "Ohne Thema"; "spaces_creation_new_rooms_general" = "Allgemein"; "spaces_creation_new_rooms_room_name_title" = "Raumname"; "spaces_creation_new_rooms_title" = "Worüber werdet ihr reden?"; @@ -2473,7 +2473,7 @@ "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."; -"location_sharing_allow_background_location_message" = "Wenn du deinen Live-Standort teilen möchtest, benötigt Element den Standortzugriff auch im Hintergrund. Um den Zugriff zu ermöglichen, tippe auf Einstellungen > Standort und wähle ‘Immer‘ aus"; +"location_sharing_allow_background_location_message" = "Wenn du deinen Echtzeit-Standort freigeben möchtest, benötigt Element den Standortzugriff auch im Hintergrund. Um den Zugriff zu gewähren, tippe auf Einstellungen > Standort und wähle „Immer“"; "space_selector_empty_view_information" = "Spaces sind eine neue Möglichkeit, Räume und Personen zu gruppieren. Erstelle einen Space, um zu beginnen."; "all_chats_onboarding_title" = "Was ist neu"; "all_chats_onboarding_page_message3" = "Drücke auf dein Profil um uns Wissen zu lassen, was du denkst."; @@ -2491,7 +2491,7 @@ "all_chats_nothing_found_placeholder_message" = "Versuche, deine Suche anzupassen."; "all_chats_edit_layout_recents" = "Historie"; "all_chats_edit_layout" = "Layouteinstellungen"; -"spaces_creation_new_rooms_message" = "Wir werden für jedes Thema einen Raum erstellen."; +"spaces_creation_new_rooms_message" = "Wir werden für jedes einen Raum erstellen."; "create_room_section_footer_type_public" = "Sichtbar und betretbar für alle eingeladenen Personen, nicht nur jene, die sich im Space befinden."; // First item is client name and second item is session display name @@ -2504,11 +2504,11 @@ "all_chats_edit_layout_add_section_message" = "Abschnitt an Startseite für schnellen Zugriff anpinnen"; "all_chats_edit_layout_add_section_title" = "Abschnitt zur Startseite hinzufügen"; "device_name_desktop" = "%@ Desktop"; -"user_sessions_overview_current_session_section_title" = "AKTUELLE SITZUNG"; -"user_sessions_overview_other_sessions_section_title" = "ANDERE SITZUNGEN"; +"user_sessions_overview_current_session_section_title" = "Aktuelle Sitzung"; +"user_sessions_overview_other_sessions_section_title" = "Andere Sitzungen"; "device_name_unknown" = "Unbekannte Anwendung"; "device_name_mobile" = "%@ Mobil"; -"user_session_item_details" = "%@ · Neueste Aktivität %@"; +"user_session_item_details" = "%1$@ · %2$@"; "user_session_unverified_additional_info" = "Verifiziere deine aktuelle Sitzung für besonders sichere Kommunikation."; "user_session_verified_additional_info" = "Deine aktuelle Sitzung ist für sichere Kommunikation bereit."; "user_session_learn_more" = "Mehr erfahren"; @@ -2516,8 +2516,126 @@ "user_session_verify_action" = "Sitzung verifizieren"; "user_session_unverified_short" = "Nicht verifiziert"; "user_session_verified_short" = "Verifiziert"; -"user_session_unverified" = "Nicht verifizierte Sitzungen"; -"user_session_verified" = "Verifizierte Sitzungen"; +"user_session_unverified" = "Nicht verifizierte Sitzung"; +"user_session_verified" = "Verifizierte Sitzung"; "user_sessions_overview_other_sessions_section_info" = "Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt."; "settings_labs_enable_new_app_layout" = "Neues App-Layout"; "room_first_message_placeholder" = "Schreibe deine erste Nachricht …"; +"room_event_encryption_info_key_authenticity_not_guaranteed" = "Die Echtheit dieser verschlüsselten Nachricht kann auf diesem Gerät nicht garantiert werden."; +"user_session_overview_session_details_button_title" = "Sitzungsdetails"; +"user_session_overview_session_title" = "Sitzung"; +"user_session_overview_current_session_title" = "Aktuelle Sitzung"; +"user_session_details_application_url" = "URL"; +"user_session_details_application_version" = "Version"; +"user_session_details_application_name" = "Name"; +"user_session_details_device_os" = "Betriebssystem"; +"user_session_details_device_browser" = "Browser"; +"user_session_details_device_model" = "Modell"; +"user_session_details_device_ip_location" = "IP-Standort"; +"user_session_details_device_ip_address" = "IP-Adresse"; +"user_session_details_session_section_footer" = "Kopiere beliebige Daten, in dem du sie gedrückt hältst."; +"user_session_details_session_id" = "Sitzungs-ID"; +"user_session_details_session_name" = "Sitzungsname"; +"user_session_details_device_section_header" = "Gerät"; +"user_session_details_application_section_header" = "Anwendung"; +"user_session_details_session_section_header" = "Sitzung"; +"user_session_details_title" = "Sitzungsdetails"; +"user_session_push_notifications_message" = "Wenn aktiviert, wird diese Sitzung Push-Benachrichtigungen erhalten."; +"user_session_push_notifications" = "Push-Benachrichtigungen"; +"user_sessions_view_all_action" = "Alle anzeigen (%1$d)"; +"user_sessions_overview_security_recommendations_inactive_info" = "Erwäge, dich aus alten (90 Tage oder mehr), nicht mehr verwendeten Sitzungen abzumelden."; +"user_sessions_overview_security_recommendations_inactive_title" = "Inaktive Sitzungen"; +"user_sessions_overview_security_recommendations_unverified_info" = "Nicht verifizierte Sitzungen verifizieren oder abmelden."; +"user_sessions_overview_security_recommendations_unverified_title" = "Nicht verifizierte Sitzungen"; +"user_sessions_overview_security_recommendations_section_info" = "Verbessere deine Kontosicherheit, indem du diese Empfehlungen beherzigst."; +"user_sessions_overview_security_recommendations_section_title" = "Sicherheitsempfehlungen"; +"all_chats_user_menu_accessibility_label" = "Benutzermenü"; +"settings_labs_enable_new_client_info_feature" = "Bezeichnung, Version und URL der Anwendung registrieren, damit diese Sitzung in der Sitzungsverwaltung besser erkennbar ist"; +"settings_labs_enable_new_session_manager" = "Neue Sitzungsverwaltung"; +"authentication_qr_login_start_step2" = "Gehe zu Einstellungen -> Sicherheit und Privatsphäre"; +"authentication_qr_login_scan_subtitle" = "Positioniere den QR-Code innerhalb des Quadrats"; +"authentication_qr_login_display_step2" = "Wähle „Anmelden mit QR-Code“"; +"authentication_qr_login_scan_title" = "QR-Code einlesen"; +"authentication_qr_login_display_subtitle" = "Lese den folgenden QR-Code mit deinem abgemeldeten Gerät ein."; +"authentication_qr_login_start_need_alternative" = "Benötigst du eine andere Methode?"; +"authentication_qr_login_start_display_qr" = "QR-Code auf diesem Gerät anzeigen"; +"authentication_qr_login_start_step4" = "Wähle „Zeige QR-Code auf diesem Gerät“"; +"authentication_qr_login_display_title" = "Verbinde ein Gerät"; +"authentication_qr_login_start_step3" = "Wähle „Verbinde ein Gerät“"; +"authentication_qr_login_start_title" = "QR-Code einlesen"; +"authentication_login_with_qr" = "Anmelden mit QR-Code"; +"device_type_name_unknown" = "Unbekannt"; +"device_type_name_mobile" = "Mobil"; +"device_type_name_web" = "Web"; +"device_type_name_desktop" = "Desktop"; +"user_inactive_session_item_with_date" = "Inaktiv seit 90+ Tagen (%@)"; +"user_inactive_session_item" = "Inaktiv seit 90+ Tagen"; +"user_other_session_unverified_sessions_header_subtitle" = "Für besonders sichere Kommunikation verifiziere deine Sitzungen oder melde dich von ihnen ab, falls du sie nicht mehr identifizieren kannst."; +"user_other_session_security_recommendation_title" = "Sicherheitsempfehlung"; +"user_sessions_overview_link_device" = "Verbinde ein Gerät"; + +// MARK: User sessions management + +// Parameter is the application display name (e.g. "Element") +"user_sessions_default_session_display_name" = "%@ iOS"; +"sign_out_confirmation_message" = "Bist du sicher, dass du dich abmelden möchtest?"; + +// MARK: Sign out warning + +"sign_out" = "Abmelden"; +"manage_session_rename" = "Sitzung umbenennen"; +"authentication_qr_login_failure_retry" = "Erneut versuchen"; +"authentication_qr_login_failure_request_timed_out" = "Die Verbindung wurde nicht in der vorgeschriebenen Zeit abgeschlossen."; +"authentication_qr_login_failure_request_denied" = "Die Anfrage wurde auf dem anderen Gerät verweigert."; +"authentication_qr_login_failure_invalid_qr" = "QR-Code ist ungültig."; +"authentication_qr_login_failure_title" = "Verbindung gescheitert"; +"authentication_qr_login_loading_signed_in" = "Du bist nun mit deinem anderen Gerät angemeldet."; +"authentication_qr_login_loading_waiting_signin" = "Warte auf Geräteanmeldung."; +"authentication_qr_login_loading_connecting_device" = "Verbinde mit Gerät"; +"authentication_qr_login_confirm_alert" = "Bitte stelle sicher, dass du die Quelle dieses Codes kennst. Durch das Verbinden des Gerätes wirst du jemandem vollen Zugriff auf dein Konto gewähren."; +"authentication_qr_login_confirm_subtitle" = "Bestätige, dass der folgende Code mit dem auf deinem anderen Gerät übereinstimmt:"; +"authentication_qr_login_confirm_title" = "Sichere Verbindung aufgebaut"; +"authentication_qr_login_display_step1" = "Öffne Element auf deinem anderen Gerät"; +"authentication_qr_login_start_step1" = "Öffne Element auf deinem anderen Gerät"; +"authentication_qr_login_start_subtitle" = "Nutze die Kamera dieses Gerätes, um den auf deinem anderen Gerät angezeigten QR-Code einzulesen:"; +"wysiwyg_composer_start_action_text_formatting" = "Textformatierung"; +"wysiwyg_composer_start_action_camera" = "Kamera"; +"wysiwyg_composer_start_action_location" = "Standort"; +"wysiwyg_composer_start_action_polls" = "Umfragen"; +"wysiwyg_composer_start_action_attachments" = "Anhänge"; +"user_session_details_last_activity" = "Neueste Aktivität"; +"user_session_item_details_last_activity" = "Neueste Aktivität %@"; +"user_other_session_clear_filter" = "Filter zurücksetzen"; +"user_other_session_no_unverified_sessions" = "Keine unverifizierten Sitzungen gefunden."; +"user_other_session_no_verified_sessions" = "Keine verifizierten Sitzungen gefunden."; +"user_other_session_no_inactive_sessions" = "Keine inaktiven Sitzungen gefunden."; +"user_other_session_filter_menu_inactive" = "Inaktiv"; +"user_other_session_filter_menu_unverified" = "Nicht verifiziert"; +"user_other_session_filter_menu_verified" = "Verifiziert"; +"user_other_session_filter_menu_all" = "Alle Sitzungen"; +"user_other_session_verified_sessions_header_subtitle" = "Für bestmögliche Sicherheit, melde dich von allen Sitzungen ab, die du nicht erkennst oder nutzt."; +"user_other_session_current_session_details" = "Deine aktuelle Sitzung"; +"user_other_session_verified_additional_info" = "Diese Sitzung ist für sichere Kommunikation bereit."; +"user_other_session_unverified_additional_info" = "Für bestmögliche Sicherheit und Zuverlässigkeit verifiziere diese Sitzung oder melde sie ab."; +"user_session_verification_unknown_additional_info" = "Verifiziere deine aktuelle Sitzung, um den Verifizierungsstatus dieser Sitzung anzuzeigen."; +"user_session_verification_unknown_short" = "Unbekannt"; +"user_session_verification_unknown" = "Unbekannter Verifizierungsstatus"; +"manage_session_name_info_link" = "Mehr erfahren"; +/* The placeholder will be replaces with manage_session_name_info_link */ +"manage_session_name_info" = "Sei dir bitte bewusst, dass Sitzungsnamen auch für Personen, mit denen du kommunizierst, sichtbar sind. %@"; +"manage_session_name_hint" = "Individuelle Sitzungsnamen können dir helfen, deine Geräte einfacher zu erkennen."; +"user_other_session_filter" = "Filtern"; +"wysiwyg_composer_format_action_strikethrough" = "Unterstrichen formatieren"; +"wysiwyg_composer_format_action_underline" = "Durchgestrichen formatieren"; +"wysiwyg_composer_format_action_italic" = "Kursiv formatieren"; + +// Formatting Actions +"wysiwyg_composer_format_action_bold" = "Fett formatieren"; +"wysiwyg_composer_start_action_stickers" = "Sticker"; + + +// Mark: - WYSIWYG Composer + +// Send Media Actions +"wysiwyg_composer_start_action_media_picker" = "Fotobibliothek"; +"settings_labs_enable_wysiwyg_composer" = "Probiere den Rich-Text-Editor aus (bald auch mit Plain-Text-Modus)"; diff --git a/Riot/Assets/en.lproj/Untranslated.strings b/Riot/Assets/en.lproj/Untranslated.strings index 9ff00a53d..9136db086 100644 --- a/Riot/Assets/en.lproj/Untranslated.strings +++ b/Riot/Assets/en.lproj/Untranslated.strings @@ -19,3 +19,5 @@ // MARK: Onboarding Personalization WIP "image_picker_action_files" = "Choose from files"; +"voice_broadcast_in_timeline_title" = "Voice broadcast detected (under active development)"; +"voice_broadcast_in_timeline_body" = "We currently only detect voice broadcast in the room timeline, this is not possible to send or listen an actual voice broadcast"; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 571cf8959..025415be3 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -156,6 +156,7 @@ "authentication_login_username" = "Username / Email / Phone"; "authentication_login_forgot_password" = "Forgot password"; "authentication_server_info_title_login" = "Where your conversations live"; +"authentication_login_with_qr" = "Sign in with QR code"; "authentication_server_selection_login_title" = "Connect to homeserver"; "authentication_server_selection_login_message" = "What is the address of your server?"; @@ -211,6 +212,37 @@ "authentication_recaptcha_title" = "Are you a human?"; +"authentication_qr_login_start_title" = "Scan QR code"; +"authentication_qr_login_start_subtitle" = "Use the camera on this device to scan the QR code shown on your other device:"; +"authentication_qr_login_start_step1" = "Open Element on your other device"; +"authentication_qr_login_start_step2" = "Go to Settings -> Security & Privacy"; +"authentication_qr_login_start_step3" = "Select ‘Link a device’"; +"authentication_qr_login_start_step4" = "Select ‘Show QR code on this device’"; +"authentication_qr_login_start_need_alternative" = "Need an alternative method?"; +"authentication_qr_login_start_display_qr" = "Show QR code on this device"; + +"authentication_qr_login_display_title" = "Link a device"; +"authentication_qr_login_display_subtitle" = "Scan the QR code below with your device that’s signed out."; +"authentication_qr_login_display_step1" = "Open Element on your other device"; +"authentication_qr_login_display_step2" = "Select ‘Sign in with QR code’"; + +"authentication_qr_login_scan_title" = "Scan QR code"; +"authentication_qr_login_scan_subtitle" = "Position the QR code in the square below"; + +"authentication_qr_login_confirm_title" = "Secure connection established"; +"authentication_qr_login_confirm_subtitle" = "Confirm that the code below matches with your other device:"; +"authentication_qr_login_confirm_alert" = "Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account."; + +"authentication_qr_login_loading_connecting_device" = "Connecting to device"; +"authentication_qr_login_loading_waiting_signin" = "Waiting for device to sign in."; +"authentication_qr_login_loading_signed_in" = "You are now signed in on your other device."; + +"authentication_qr_login_failure_title" = "Linking failed"; +"authentication_qr_login_failure_invalid_qr" = "QR code is invalid."; +"authentication_qr_login_failure_request_denied" = "The request was denied on the other device."; +"authentication_qr_login_failure_request_timed_out" = "The linking wasn’t completed in the required time."; +"authentication_qr_login_failure_retry" = "Try again"; + // MARK: Password Validation "password_validation_info_header" = "Your password should meet the criteria below:"; "password_validation_error_header" = "Given password does not meet the criteria below:"; @@ -765,6 +797,8 @@ Tap the + to start adding people."; "settings_labs_enable_new_session_manager" = "New session manager"; "settings_labs_enable_new_client_info_feature" = "Record the client name, version, and url to recognise sessions more easily in session manager"; "settings_labs_enable_new_app_layout" = "New Application Layout"; +"settings_labs_enable_wysiwyg_composer" = "Try out the rich text editor (plain text mode coming soon)"; +"settings_labs_enable_voice_broadcast" = "Voice broadcast (under active development). We currently only detect voice broadcast in the room timeline, this is not possible to send or listen an actual voice broadcast"; "settings_version" = "Version %@"; "settings_olm_version" = "Olm Version %@"; @@ -894,9 +928,14 @@ Tap the + to start adding people."; "manage_session_title" = "Manage session"; "manage_session_info" = "SESSION INFO"; "manage_session_name" = "Session name"; +"manage_session_name_hint" = "Custom session names can help you recognize your devices more easily."; +/* The placeholder will be replaces with manage_session_name_info_link */ +"manage_session_name_info" = "Please be aware that session names are also visible to people you communicate with. %@"; +"manage_session_name_info_link" = "Learn more"; "manage_session_trusted" = "Trusted by you"; "manage_session_not_trusted" = "Not trusted"; "manage_session_sign_out" = "Sign out of this session"; +"manage_session_rename" = "Rename session"; // User sessions management "user_sessions_settings" = "Manage sessions"; @@ -1449,6 +1488,9 @@ Tap the + to start adding people."; // MARK: Sign out warning +"sign_out" = "Sign out"; +"sign_out_confirmation_message" = "Are you sure you want to sign out?"; + "sign_out_existing_key_backup_alert_title" = "Are you sure you want to sign out?"; "sign_out_existing_key_backup_alert_sign_out_action" = "Sign out"; @@ -2356,6 +2398,9 @@ To enable access, tap Settings> Location and select Always"; // MARK: User sessions management +// Parameter is the application display name (e.g. "Element") +"user_sessions_default_session_display_name" = "%@ iOS"; + "user_sessions_overview_title" = "Sessions"; "user_sessions_overview_security_recommendations_section_title" = "Security recommendations"; @@ -2371,33 +2416,62 @@ To enable access, tap Settings> Location and select Always"; "user_sessions_overview_other_sessions_section_info" = "For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore."; "user_sessions_overview_current_session_section_title" = "Current session"; +"user_sessions_overview_link_device" = "Link a device"; "user_sessions_view_all_action" = "View all (%d)"; "user_session_verified" = "Verified session"; "user_session_unverified" = "Unverified session"; +"user_session_verification_unknown" = "Unknown verification status"; "user_session_verified_short" = "Verified"; "user_session_unverified_short" = "Unverified"; +"user_session_verification_unknown_short" = "Unknown"; "user_session_verify_action" = "Verify session"; "user_session_view_details" = "View details"; "user_session_learn_more" = "Learn more"; "user_session_verified_additional_info" = "Your current session is ready for secure messaging."; "user_session_unverified_additional_info" = "Verify your current session for enhanced secure messaging."; - +"user_session_verification_unknown_additional_info" = "Verify your current session to reveal this session's verification status."; +"user_other_session_unverified_additional_info" = "Verify or sign out from this session for best security and reliability."; +"user_other_session_verified_additional_info" = "This session is ready for secure messaging."; "user_session_push_notifications" = "Push notifications"; "user_session_push_notifications_message" = "When turned on, this session will receive push notifications."; +"user_other_session_security_recommendation_title" = "Security recommendation"; +"user_other_session_unverified_sessions_header_subtitle" = "Verify your sessions for enhanced secure messaging or sign out from those you don’t recognize or use anymore."; +"user_other_session_current_session_details" = "Your current session"; +"user_other_session_verified_sessions_header_subtitle" = "For best security, sign out from any session that you don’t recognize or use anymore."; + +"user_other_session_filter" = "Filter"; +"user_other_session_filter_menu_all" = "All sessions"; +"user_other_session_filter_menu_verified" = "Verified"; +"user_other_session_filter_menu_unverified" = "Unverified"; +"user_other_session_filter_menu_inactive" = "Inactive"; + +"user_other_session_no_inactive_sessions" = "No inactive sessions found."; +"user_other_session_no_verified_sessions" = "No verified sessions found."; +"user_other_session_no_unverified_sessions" = "No unverified sessions found."; +"user_other_session_clear_filter" = "Clear filter"; // First item is client name and second item is session display name "user_session_name" = "%@: %@"; -"user_session_item_details" = "%@ · Last activity %@"; +/* %1$@ will be the verification state and %2$@ will be user_session_item_details_verification_unknown or user_other_session_current_session_details */ +"user_session_item_details" = "%1$@ · %2$@"; +"user_session_item_details_last_activity" = "Last activity %@"; +"user_inactive_session_item" = "Inactive for 90+ days"; +"user_inactive_session_item_with_date" = "Inactive for 90+ days (%@)"; "device_name_desktop" = "%@ Desktop"; "device_name_web" = "%@ Web"; "device_name_mobile" = "%@ Mobile"; "device_name_unknown" = "Unknown client"; +"device_type_name_desktop" = "Desktop"; +"device_type_name_web" = "Web"; +"device_type_name_mobile" = "Mobile"; +"device_type_name_unknown" = "Unknown"; + "user_session_details_title" = "Session details"; "user_session_details_session_section_header" = "Session"; "user_session_details_application_section_header" = "Application"; @@ -2405,6 +2479,7 @@ To enable access, tap Settings> Location and select Always"; "user_session_details_session_name" = "Session name"; "user_session_details_session_id" = "Session ID"; "user_session_details_session_section_footer" = "Copy any data by tapping on it and holding it down."; +"user_session_details_last_activity" = "Last activity"; "user_session_details_device_ip_address" = "IP address"; "user_session_details_device_ip_location" = "IP location"; "user_session_details_device_model" = "Model"; @@ -2416,6 +2491,25 @@ To enable access, tap Settings> Location and select Always"; "user_session_overview_current_session_title" = "Current session"; "user_session_overview_session_title" = "Session"; "user_session_overview_session_details_button_title" = "Session details"; + + +// Mark: - WYSIWYG Composer + +// Send Media Actions +"wysiwyg_composer_start_action_media_picker" = "Photo Library"; +"wysiwyg_composer_start_action_stickers" = "Stickers"; +"wysiwyg_composer_start_action_attachments" = "Attachments"; +"wysiwyg_composer_start_action_polls" = "Polls"; +"wysiwyg_composer_start_action_location" = "Location"; +"wysiwyg_composer_start_action_camera" = "Camera"; +"wysiwyg_composer_start_action_text_formatting" = "Text Formatting"; + +// Formatting Actions +"wysiwyg_composer_format_action_bold" = "Apply bold format"; +"wysiwyg_composer_format_action_italic" = "Apply italic format"; +"wysiwyg_composer_format_action_underline" = "Apply strikethrough format"; +"wysiwyg_composer_format_action_strikethrough" = "Apply underline format"; + // MARK: - MatrixKit diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings index d84210c12..f9bdd08c5 100644 --- a/Riot/Assets/et.lproj/Vector.strings +++ b/Riot/Assets/et.lproj/Vector.strings @@ -2441,7 +2441,7 @@ "device_name_mobile" = "%@ Mobile"; "device_name_web" = "%@ Web"; "device_name_desktop" = "%@ Desktop"; -"user_session_item_details" = "%@ · Viimati kasutusel %@"; +"user_session_item_details" = "%@ · %@"; // First item is client name and second item is session display name "user_session_name" = "%@: %@"; @@ -2454,8 +2454,55 @@ "user_session_verified_short" = "Verifitseeritud"; "user_session_unverified" = "Verifitseerimata sessioon"; "user_session_verified" = "Verifitseeritud sessioon"; -"user_sessions_overview_current_session_section_title" = "PRAEGUNE SESSIOON"; +"user_sessions_overview_current_session_section_title" = "Praegune sessioon"; "user_sessions_overview_other_sessions_section_info" = "Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta."; -"user_sessions_overview_other_sessions_section_title" = "MUUD SESSIOONID"; +"user_sessions_overview_other_sessions_section_title" = "Muud sessioonid"; "settings_labs_enable_new_app_layout" = "Rakenduse uus paigutus"; "room_first_message_placeholder" = "Saada oma esimene sõnum…"; +"room_event_encryption_info_key_authenticity_not_guaranteed" = "Selle krüptitud sõnumi autentsus pole selles seadmes tagatud."; +"user_session_overview_session_details_button_title" = "Sessiooni teave"; +"user_session_overview_session_title" = "Sessioon"; +"user_session_overview_current_session_title" = "Praegune sessioon"; +"user_session_details_application_url" = "URL"; +"user_session_details_application_version" = "Versioon"; +"user_session_details_application_name" = "Nimi"; +"user_session_details_device_os" = "Operatsioonisüsteem"; +"user_session_details_device_browser" = "Brauser"; +"user_session_details_device_model" = "Mudel"; +"user_session_details_device_ip_location" = "IP-aadressi asukoht"; +"user_session_details_device_ip_address" = "IP-aadress"; +"user_session_details_session_section_footer" = "Pika vajutusega saad kopeerida andmeid."; +"user_session_details_session_id" = "Sessiooni tunnus"; +"user_session_details_session_name" = "Sessiooni nimi"; +"user_session_details_device_section_header" = "Seade"; +"user_session_details_application_section_header" = "Rakendus"; +"user_session_details_session_section_header" = "Sessioon"; +"user_session_details_title" = "Sessiooni teave"; +"user_session_push_notifications_message" = "Kui see valik on sisse lülitatud, siis see sessioon saab vastu võtta tõuketeavitusi."; +"user_session_push_notifications" = "Tõuketeavitused"; +"user_sessions_view_all_action" = "Näita kõiki (%d)"; +"user_sessions_overview_security_recommendations_inactive_info" = "Logi välja sellistest vanadest sessioonidest (vanemad kui 90 päeva), mida sa enam ei kasuta."; +"user_sessions_overview_security_recommendations_inactive_title" = "Mitteaktiivsed sessioonid"; +"user_sessions_overview_security_recommendations_unverified_info" = "Logi verifitseerimata sessioonidest välja või verifitseeri nad."; +"user_sessions_overview_security_recommendations_unverified_title" = "Verifitseerimata sessioonid"; +"user_sessions_overview_security_recommendations_section_info" = "Kui järgid neid soovitusi, siis sa parandad oma kasutajakonto turvalisust."; +"user_sessions_overview_security_recommendations_section_title" = "Turvalisusega seotud soovitused"; +"all_chats_user_menu_accessibility_label" = "Kasutajamenüü"; +"settings_labs_enable_new_client_info_feature" = "Sessioonide paremaks tuvastamiseks saad nüüd sessioonihalduris salvestada klientrakenduse nime, versiooni ja aadressi"; +"settings_labs_enable_new_session_manager" = "Uus sessioonihaldur"; +"authentication_qr_login_confirm_title" = "Turvaline ühendus on olemas"; +"authentication_qr_login_scan_subtitle" = "Joonda QR-kood allpool näidatud ruudu sisse"; +"authentication_qr_login_scan_title" = "Skaneeri QR-koodi"; +"authentication_qr_login_display_step2" = "Vali „Logi võrku QR-koodi abil“"; +"authentication_qr_login_display_step1" = "Ava Element oma teises seadmes"; +"authentication_qr_login_display_subtitle" = "Loe QR-koodi seadmega, kus sa oled Matrix'i võrgust välja loginud."; +"authentication_qr_login_display_title" = "Seo teise seadmega"; +"authentication_qr_login_start_display_qr" = "Näita selles seadmes QR-koodi"; +"authentication_qr_login_start_need_alternative" = "Kas soovid kasutada mõnda muud lahendust?"; +"authentication_qr_login_start_step4" = "Vali „Näita selles seadmes QR-koodi“"; +"authentication_qr_login_start_step3" = "Vali „Seo seade“"; +"authentication_qr_login_start_step2" = "Vali menüüst Seadistused -> Turvalisus ja privaatsus"; +"authentication_qr_login_start_step1" = "Ava Element mõnes oma muus seadmes"; +"authentication_qr_login_start_subtitle" = "Kasuta selle seadme kaamerat ja logi sisse teises seadmes kuvatud QR-koodi alusel:"; +"authentication_qr_login_start_title" = "Loe QR-koodi"; +"authentication_login_with_qr" = "Logi sisse QR-koodi abil"; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 3cf3a6a40..bb02d69cd 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -1044,7 +1044,7 @@ "security_settings_crosssigning_reset" = "Réinitialiser"; "security_settings_crosssigning_complete_security" = "Compléter la sécurité"; "security_settings_complete_security_alert_title" = "Améliorer la sécurité"; -"security_settings_complete_security_alert_message" = "Vous devriez d’abord améliorer la sécurité de votre session actuelle."; +"security_settings_complete_security_alert_message" = "Vous devriez d’abord améliorer la sécurité de la session courante."; "security_settings_coming_soon" = "Désolé, cette action n’est pas encore disponible dans %@ iOS. Utilisez un autre client Matrix pour le configurer. %@ iOS l’utilisera."; "device_verification_self_verify_wait_new_sign_in_title" = "Vérifier cette connexion"; "device_verification_self_verify_wait_additional_information" = "Ceci fonctionne avec %@ ou un autre client Matrix prenant en charge la signature croisée."; @@ -2506,3 +2506,102 @@ // User sessions management "user_sessions_settings" = "Gérer les sessions"; "invite_to" = "Inviter dans %@"; +"user_session_unverified_additional_info" = "Vérifiez cette session pour renforcer la sécurité de votre messagerie."; +"user_session_verified_additional_info" = "Cette session est prête pour la messagerie sécurisée."; +"room_event_encryption_info_key_authenticity_not_guaranteed" = "L’authenticité de ce message chiffré ne peut être garantie sur cet appareil."; +"user_session_overview_session_details_button_title" = "Informations de session"; +"user_session_overview_session_title" = "Session"; +"user_session_overview_current_session_title" = "Session courante"; +"user_session_details_application_url" = "URL"; +"user_session_details_application_version" = "Version"; +"user_session_details_application_name" = "Nom"; +"user_session_details_device_os" = "Système d’exploitation"; +"user_session_details_device_browser" = "Navigateur"; +"user_session_details_device_model" = "Modèle"; +"user_session_details_device_ip_location" = "Emplacement de l’IP"; +"user_session_details_device_ip_address" = "Adresse IP"; +"user_session_details_session_section_footer" = "Copiez n’importe quelles données en faisant une pression longue dessus."; +"user_session_details_session_id" = "Identifiant de la session"; +"user_session_details_session_name" = "Nom de la session"; +"user_session_details_device_section_header" = "Appareil"; +"user_session_details_application_section_header" = "Application"; +"user_session_details_session_section_header" = "Session"; +"user_session_details_title" = "Informations de session"; +"device_type_name_unknown" = "Inconnu"; +"device_type_name_mobile" = "Mobile"; +"device_type_name_web" = "Web"; +"device_type_name_desktop" = "Bureau"; +"device_name_unknown" = "Client inconnu"; +"device_name_mobile" = "%@ Mobile"; +"device_name_web" = "%@ Web"; +"device_name_desktop" = "%@ Bureau"; +"user_inactive_session_item_with_date" = "Inactif depuis 90 jours ou plus (%@)"; +"user_inactive_session_item" = "Inactif depuis 90 jours ou plus"; +"user_session_item_details" = "%@ · Dernière activité %@"; + +// First item is client name and second item is session display name +"user_session_name" = "%@ : %@"; +"user_other_session_unverified_sessions_header_subtitle" = "Vérifiez vos sessions pour renforcer la sécurité de votre messagerie, ou déconnectez celles que vous ne reconnaissez ou utilisez plus."; +"user_other_session_security_recommendation_title" = "Recommandations de sécurité"; +"user_session_push_notifications_message" = "Lorsqu’activé, cette session recevra des notifications push."; +"user_session_push_notifications" = "Notifications push"; +"user_sessions_overview_current_session_section_title" = "Session courante"; +"user_session_learn_more" = "En apprendre plus"; +"user_session_view_details" = "Afficher les informations"; +"user_session_verify_action" = "Vérifier la session"; +"user_session_unverified_short" = "Non-vérifiée"; +"user_session_verified_short" = "Vérifiée"; +"user_session_unverified" = "Session non-vérifiée"; +"user_session_verified" = "Session vérifiée"; +"user_sessions_view_all_action" = "Tout afficher (%d)"; +"user_sessions_overview_link_device" = "Appairer un appareil"; +"user_sessions_overview_other_sessions_section_info" = "Pour une sécurité optimale, vérifiez vos sessions et déconnectez celles que vous de reconnaissez pas ou n’utilisez plus."; +"user_sessions_overview_other_sessions_section_title" = "Autres sessions"; +"user_sessions_overview_security_recommendations_inactive_info" = "Vous pourriez vouloir déconnecter les anciennes sessions que vous n’utilisez plus (depuis au moins 90 jours)."; +"user_sessions_overview_security_recommendations_inactive_title" = "Sessions inactives"; +"user_sessions_overview_security_recommendations_unverified_info" = "Vérifiez ou déconnectez les sessions non-vérifiées."; +"user_sessions_overview_security_recommendations_unverified_title" = "Sessions non-vérifiées"; +"user_sessions_overview_security_recommendations_section_info" = "Renforcez la sécurité de votre compte en suivant ces recommandations."; +"user_sessions_overview_security_recommendations_section_title" = "Recommandations de sécurité"; + +// MARK: User sessions management + +// Parameter is the application display name (e.g. "Element") +"user_sessions_default_session_display_name" = "%@ iOS"; +"all_chats_user_menu_accessibility_label" = "Menu utilisateur"; +"sign_out_confirmation_message" = "Êtes vous sûr de vouloir vous déconnecter ?"; + +// MARK: Sign out warning + +"sign_out" = "Déconnexion"; +"manage_session_rename" = "Renommer la session"; +"settings_labs_enable_new_app_layout" = "Nouvelle disposition"; +"settings_labs_enable_new_client_info_feature" = "Renseignez le nom du client, sa version, et son URL pour retrouvez vos sessions plus facilement dans le gestionnaire de sessions"; +"settings_labs_enable_new_session_manager" = "Nouveau gestionnaire de sessions"; +"room_first_message_placeholder" = "Envoyez votre premier message…"; +"authentication_qr_login_failure_retry" = "Réessayez"; +"authentication_qr_login_failure_request_timed_out" = "L’appairage n’a pas été effectué dans le temps imparti."; +"authentication_qr_login_failure_request_denied" = "La requête a été refusée sur l’autre appareil."; +"authentication_qr_login_failure_invalid_qr" = "Le QR code est invalide."; +"authentication_qr_login_failure_title" = "Échec de l’appairage"; +"authentication_qr_login_loading_signed_in" = "Vous êtes désormais connecté sur votre autre appareil."; +"authentication_qr_login_loading_waiting_signin" = "En attente de connexion de l’appareil."; +"authentication_qr_login_loading_connecting_device" = "Connexion à l’appareil"; +"authentication_qr_login_confirm_alert" = "Vérifiez l’origine de ce code. En appairant un appareil, vous lui fournissez un accès complet à votre compte."; +"authentication_qr_login_confirm_subtitle" = "Confirmez que le code ci-dessous correspond à celui sur votre autre appareil :"; +"authentication_qr_login_confirm_title" = "Connexion sécurisée établie"; +"authentication_qr_login_scan_subtitle" = "Placez le QR code dans le carré ci-dessous"; +"authentication_qr_login_scan_title" = "Scannez le QR code"; +"authentication_qr_login_display_step2" = "Sélectionnez « Se connecter avec un QR code »"; +"authentication_qr_login_display_step1" = "Ouvrez Element sur votre autre appareil"; +"authentication_qr_login_display_subtitle" = "Scannez le QR code ci-dessous avec l’appareil qui n’est pas connecté."; +"authentication_qr_login_display_title" = "Appairer un appareil"; +"authentication_qr_login_start_display_qr" = "Afficher le QR code sur cet appareil"; +"authentication_qr_login_start_need_alternative" = "Besoin d’une autre méthode ?"; +"authentication_qr_login_start_step4" = "Sélectionnez « Afficher le QR code sur cet appareil »"; +"authentication_qr_login_start_step3" = "Sélectionnez « Appairer un appareil »"; +"authentication_qr_login_start_step2" = "Allez dans Réglages -> Confidentialité et sécurité"; +"authentication_qr_login_start_step1" = "Ouvrez Element sur votre autre appareil"; +"authentication_qr_login_start_subtitle" = "Utilisez l’appareil photo de cet appareil pour scanner le QR code affiché sur votre autre appareil :"; +"authentication_qr_login_start_title" = "Scanner le QR code"; +"authentication_login_with_qr" = "Se connecter avec un QR code"; diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 87b74c5bb..dc87963f7 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -2489,7 +2489,7 @@ "device_name_mobile" = "%@ Mobil"; "device_name_desktop" = "%@ Alkalmazás"; "device_name_web" = "%@ Web"; -"user_session_item_details" = "%@ · Utolsó aktivitás %@"; +"user_session_item_details" = "%1$@ · %2$@"; // First item is client name and second item is session display name "user_session_name" = "%@: %@"; @@ -2502,8 +2502,126 @@ "user_session_verified_short" = "Hitelesített"; "user_session_unverified" = "Ellenőrizetlen munkamenet"; "user_session_verified" = "Ellenőrzött munkamenet"; -"user_sessions_overview_current_session_section_title" = "JELENLEGI MUNKAMENET"; +"user_sessions_overview_current_session_section_title" = "Jelenlegi munkamenet"; "user_sessions_overview_other_sessions_section_info" = "A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz."; -"user_sessions_overview_other_sessions_section_title" = "TOVÁBBI MUNKAMENETEK"; +"user_sessions_overview_other_sessions_section_title" = "További munkamenetek"; "settings_labs_enable_new_app_layout" = "Új alkalmazás kinézet"; "room_first_message_placeholder" = "Küld el az első üzenetedet…"; +"authentication_qr_login_confirm_title" = "Biztonságos kapcsolat beállítva"; +"room_event_encryption_info_key_authenticity_not_guaranteed" = "A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni."; +"wysiwyg_composer_format_action_strikethrough" = "Aláhúzott"; +"wysiwyg_composer_format_action_underline" = "Áthúzott"; +"wysiwyg_composer_format_action_italic" = "Dőlt"; + +// Formatting Actions +"wysiwyg_composer_format_action_bold" = "Félkövér"; +"wysiwyg_composer_start_action_text_formatting" = "Szöveg formázás"; +"wysiwyg_composer_start_action_camera" = "Kamera"; +"wysiwyg_composer_start_action_location" = "Földrajzi helyzet"; +"wysiwyg_composer_start_action_polls" = "Szavazások"; +"wysiwyg_composer_start_action_attachments" = "Mellékletek"; +"wysiwyg_composer_start_action_stickers" = "Matricák"; + + +// Mark: - WYSIWYG Composer + +// Send Media Actions +"wysiwyg_composer_start_action_media_picker" = "Fénykép könyvtár"; +"user_session_overview_session_details_button_title" = "Munkamenet információk"; +"user_session_overview_session_title" = "Munkamenet"; +"user_session_overview_current_session_title" = "Jelenlegi munkamenet"; +"user_session_details_application_url" = "URL"; +"user_session_details_application_version" = "Verzió"; +"user_session_details_application_name" = "Név"; +"user_session_details_device_os" = "Operációs rendszer"; +"user_session_details_device_browser" = "Böngésző"; +"user_session_details_device_model" = "Modell"; +"user_session_details_device_ip_location" = "Tartózkodási helyem"; +"user_session_details_device_ip_address" = "IP cím"; +"user_session_details_last_activity" = "Utolsó tevékenység"; +"user_session_details_session_section_footer" = "A másoláshoz koppints és tartsd rajta az ujjad."; +"user_session_details_session_id" = "Kapcsolat azonosító"; +"user_session_details_session_name" = "Munkamenet neve"; +"user_session_details_device_section_header" = "Eszköz"; +"user_session_details_application_section_header" = "Alkalmazás"; +"user_session_details_session_section_header" = "Munkamenet"; +"user_session_details_title" = "Munkamenet információk"; +"device_type_name_unknown" = "Ismeretlen"; +"device_type_name_mobile" = "Mobil"; +"device_type_name_web" = "Web"; +"device_type_name_desktop" = "Asztali"; +"user_inactive_session_item_with_date" = "90+ napja inaktív (%@)"; +"user_inactive_session_item" = "90+ napja inaktív"; +"user_session_item_details_last_activity" = "Utolsó tevékenység: %@"; +"user_other_session_clear_filter" = "Szűrő törlése"; +"user_other_session_no_unverified_sessions" = "Nincs ellenőrizetlen munkamenet."; +"user_other_session_no_verified_sessions" = "Nincs ellenőrzött munkamenet."; +"user_other_session_no_inactive_sessions" = "Nincs inaktív munkamenet."; +"user_other_session_filter_menu_inactive" = "Inaktív"; +"user_other_session_filter_menu_unverified" = "Ellenőrizetlen"; +"user_other_session_filter_menu_verified" = "Hitelesített"; +"user_other_session_filter_menu_all" = "Minden munkamenet"; +"user_other_session_filter" = "Szűrés"; +"user_other_session_verified_sessions_header_subtitle" = "A legjobb biztonság érdekében jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz."; +"user_other_session_current_session_details" = "Jelenlegi munkamenet"; +"user_other_session_unverified_sessions_header_subtitle" = "Erősítse meg a munkameneteit a még biztonságosabb csevegéshez vagy jelentkezzen ki ezekből, ha nem ismeri fel vagy már nem használja őket."; +"user_other_session_security_recommendation_title" = "Biztonsági javaslat"; +"user_session_push_notifications_message" = "Ha be van kapcsolva az eszközre Push értesítések lesznek küldve."; +"user_session_push_notifications" = "Push értesítések"; +"user_other_session_verified_additional_info" = "Ez a munkamenet beállítva a biztonságos üzenetküldéshez."; +"user_other_session_unverified_additional_info" = "A jobb biztonság vagy megbízhatóság érdekében ellenőrizze vagy jelentkezzen ki ebből a munkamenetből."; +"user_session_verification_unknown_additional_info" = "Ellenőrizd a jelenlegi munkamenetedet, hogy ismert állapotba kerüljön."; +"user_session_verification_unknown_short" = "Ismeretlen"; +"user_session_verification_unknown" = "Ismeretlen ellenőrzési státusz"; +"user_sessions_view_all_action" = "Összes megtekintése (%d)"; +"user_sessions_overview_link_device" = "Eszköz összekötése"; +"user_sessions_overview_security_recommendations_inactive_info" = "Fontold meg, hogy kijelentkezel a régi munkamenetekből (90 napja vagy régebben használtál) amit már nem használsz."; +"user_sessions_overview_security_recommendations_inactive_title" = "Nem aktív munkamenetek"; +"user_sessions_overview_security_recommendations_unverified_info" = "Ellenőrizd vagy jelentkezz ki az ellenőrizetlen munkamenetekből."; +"user_sessions_overview_security_recommendations_unverified_title" = "Ellenőrizetlen munkamenetek"; +"user_sessions_overview_security_recommendations_section_info" = "Javítsa a fiókja biztonságát azzal, hogy követi a következő javaslatokat."; +"user_sessions_overview_security_recommendations_section_title" = "Biztonsági javaslatok"; + +// MARK: User sessions management + +// Parameter is the application display name (e.g. "Element") +"user_sessions_default_session_display_name" = "%@ iOS"; +"all_chats_user_menu_accessibility_label" = "Felhasználói menü"; +"sign_out_confirmation_message" = "Biztos, hogy ki akarsz lépni?"; + +// MARK: Sign out warning + +"sign_out" = "Kijelentkezés"; +"manage_session_rename" = "Munkamenet átnevezése"; +"manage_session_name_info_link" = "Tudj meg többet"; +/* The placeholder will be replaces with manage_session_name_info_link */ +"manage_session_name_info" = "Fontos, hogy a munkamenet neve a kommunikációban résztvevők számára látható. %@"; +"manage_session_name_hint" = "Az egyedi munkamenet név segíthet az eszköz könnyebb felismerésében."; +"settings_labs_enable_wysiwyg_composer" = "Próbálja ki az új szövegbevitelt (hamarosan érkezik a sima szöveges üzemmód)"; +"settings_labs_enable_new_client_info_feature" = "Kliens neve, verziója és url felvétele a munkamenet könnyebb azonosításához a munkamenet kezelőben"; +"settings_labs_enable_new_session_manager" = "Új munkamenet kezelő"; +"authentication_qr_login_failure_retry" = "Próbáld újra"; +"authentication_qr_login_failure_request_timed_out" = "Az összekötés az elvárt időn belül nem fejeződött be."; +"authentication_qr_login_failure_request_denied" = "A kérést elutasították a másik eszközön."; +"authentication_qr_login_failure_invalid_qr" = "QR kód érvénytelen."; +"authentication_qr_login_failure_title" = "Összekötés sikertelen"; +"authentication_qr_login_loading_signed_in" = "Bejelentkeztél a másik eszközöddel."; +"authentication_qr_login_loading_waiting_signin" = "Várakozás a másik eszköz bejelentkezésére."; +"authentication_qr_login_loading_connecting_device" = "Csatlakozás az eszközhöz"; +"authentication_qr_login_confirm_alert" = "Győződj meg a kód eredetéről. Az eszközök összekötésével esetleg valakinek teljes hozzáférést adhatsz a fiókodhoz."; +"authentication_qr_login_confirm_subtitle" = "Erősítsd meg, hogy az alábbi kód megegyezik a másik eszközödön lévővel:"; +"authentication_qr_login_scan_subtitle" = "A QR kód legyen a négyzetben"; +"authentication_qr_login_scan_title" = "QR kód beolvasása"; +"authentication_qr_login_display_step2" = "Válaszd ezt: „Belépés QR kóddal”"; +"authentication_qr_login_display_step1" = "Nyisd meg az Elementet a másik eszközödön"; +"authentication_qr_login_display_subtitle" = "A kijelentkezett eszközzel olvasd be a QR kódot alább."; +"authentication_qr_login_display_title" = "Eszköz összekötése"; +"authentication_qr_login_start_display_qr" = "QR kód megjelenítése ezen az eszközön"; +"authentication_qr_login_start_need_alternative" = "Más módszer szükséges?"; +"authentication_qr_login_start_step4" = "Válaszd ezt: „QR kód megjelenítése ezen az eszközön”"; +"authentication_qr_login_start_step3" = "Válaszd ezt: „Eszköz összekötése”"; +"authentication_qr_login_start_step2" = "Menj a Beállítások -> Biztonság és Adatvédelem menübe"; +"authentication_qr_login_start_step1" = "Nyisd meg az Elementet a másik eszközödön"; +"authentication_qr_login_start_subtitle" = "Használd a kamerát ezen az eszközön a másik eszközödön megjelenő QR kód beolvasására:"; +"authentication_qr_login_start_title" = "QR kód beolvasása"; +"authentication_login_with_qr" = "Belépés QR kóddal"; diff --git a/Riot/Assets/id.lproj/Vector.strings b/Riot/Assets/id.lproj/Vector.strings index d698f6115..210d60bb3 100644 --- a/Riot/Assets/id.lproj/Vector.strings +++ b/Riot/Assets/id.lproj/Vector.strings @@ -70,7 +70,7 @@ // Titles "title_home" = "Beranda"; -"auth_email_validation_message" = "Silakan periksa surel Anda untuk melanjutkan pendaftaran"; +"auth_email_validation_message" = "Silakan periksa email Anda untuk melanjutkan pendaftaran"; "auth_use_server_options" = "Gunakan opsi server khusus (lanjutan)"; "auth_email_not_found" = "Gagal mengirim surel: Alamat email ini tidak ditemukan"; "auth_forgot_password_error_no_configured_identity_server" = "Tidak ada server identitas yang dikonfigurasikan: tambahkan satu untuk mengatur ulang kata sandi akun Matrix Anda."; @@ -89,9 +89,9 @@ "auth_add_phone_message_2" = "Atur telepon, dan nanti dapat ditemukan oleh orang-orang yang mengenal Anda secara opsional."; "auth_add_email_message_2" = "Tetapkan surel untuk pemulihan akun, dan nanti dapat ditemukan oleh orang-orang yang mengenal Anda secara opsional."; "auth_missing_password" = "Tidak ada kata sandi"; -"auth_invalid_phone" = "Ini tidak terlihat seperti nomor telepon yang valid"; -"auth_invalid_email" = "Ini tidak terlihat seperti surel yang valid"; -"auth_invalid_password" = "Kata sandi terlalu pendek (min 6)"; +"auth_invalid_phone" = "Ini tidak terlihat seperti nomor telepon yang absah"; +"auth_invalid_email" = "Ini tidak terlihat seperti alamat email yang absah"; +"auth_invalid_password" = "Kata sandi terlalu pendek (min. 6)"; "auth_invalid_user_name" = "Nama pengguna hanya dapat berisi huruf, angka, titik, tanda hubung, dan garis bawah"; "auth_send_reset_email" = "Kirim Reset Email"; "auth_submit" = "Kirim"; @@ -102,7 +102,7 @@ "joined" = "Bergabung"; "collapse" = "tutup"; "store_promotional_text" = "Aplikasi perpesanan dan kolaborasi yang menjaga privasi, pada jaringan terbuka. Terdesentralisasi untuk Anda kendali. Tidak ada penambangan data, tidak ada pintu belakang dan tidak ada akses pihak ketiga."; -"store_full_description" = "Element adalah aplikasi messenger dan kolaborasi tipe baru yang:\n\n1. Menempatkan Anda dalam kendali untuk mempertahankan privasi Anda\n2. Memungkinkan Anda berkomunikasi dengan siapa pun di jaringan Matrix, dan bahkan di luar dengan mengintegrasikan dengan aplikasi seperti Slack\n3. Melindungi Anda dari iklan, menambangan data, pintu belakang, dan taman berdinding\n4. Mengamankan Anda melalui enkripsi ujung-ke-ujung, dengan penandatanganan silang untuk memverifikasi orang lain\n\nElement benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lain karena Element terdesentralisasi dan sumber terbuka.\n\nElement memungkinkan Anda host sendiri — atau memilih host — sehingga Anda memiliki privasi, kepemilikan, dan kontrol data dan obrolan Anda. Ini memberi Anda akses ke jaringan terbuka, jadi Anda tidak hanya terjebak berbicara dengan pengguna Element. Itu sangat aman.\n\nElement dapat melakukan semua ini karena beroperasi pada Matrix — standar untuk komunikasi terdesentralisasi terbuka.\n\nElement menempatkan Anda dalam kendali dengan membiarkan Anda memilih siapa yang menghost percakapan Anda. Dari aplikasi Element, Anda dapat memilih untuk menghost dengan cara yang berbeda:\n\n1. Dapatkan akun gratis pada server publik matrix.org\n2. Host sendiri akun Anda dengan menjalankan server pada perangkat keras Anda sendiri\n3. Mendaftar untuk akun di server khusus dengan hanya berlangganan platform hosting Element Matrix Services\n\nMengapa memilih Element?\n\nMILIKI DATA ANDA: Anda memutuskan di mana untuk menyimpan data dan pesan Anda. Anda memilikinya dan mengendalikannya, bukan perusahaan besar yang menambang data Anda atau memberikan akses ke pihak ketiga.\n\nPESAN DAN KOLABORASI TERBUKA: Anda dapat mengobrol dengan orang lain di jaringan Matrix, jika mereka menggunakan Element atau aplikasi Matrix lain, dan bahkan jika mereka menggunakan sistem perpesanan seperti Slack, IRC atau XMPP.\n\nSANGAT AMAN: Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan penandatanganan silang untuk memverifikasi perangkat anggota obrolan.\n\nKOMUNIKASI LENGKAP: Perpesanan, panggilan suara dan video, pembagian file, pembagian layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.\n\nDI MANA PUN ANDA BERADA: Tetap berkomunikasi di mana pun Anda berada dengan riwayat pesan yang sepenuhnya disinkronkan di semua perangkat Anda dan di web di https://app.element.io/."; +"store_full_description" = "Element adalah aplikasi perpesanan dan kolaborasi baru yang:\n\n1. Menempatkan Anda dalam kendali untuk mempertahankan privasi Anda\n2. Memungkinkan Anda berkomunikasi dengan siapa pun di jaringan Matrix, dan bahkan di luar dengan mengintegrasikan dengan aplikasi seperti Slack\n3. Melindungi Anda dari iklan, menambangan data, pintu belakang, dan taman berdinding\n4. Mengamankan Anda melalui enkripsi ujung ke ujung, dengan penandatanganan silang untuk memverifikasi orang lain\n\nElement benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lain karena Element terdesentralisasi dan sumber terbuka.\n\nElement memungkinkan Anda host sendiri — atau memilih host — sehingga Anda memiliki privasi, kepemilikan, dan kontrol data dan obrolan Anda. Ini memberi Anda akses ke jaringan terbuka, jadi Anda tidak hanya terjebak berbicara dengan pengguna Element. Itu sangat aman.\n\nElement dapat melakukan semua ini karena beroperasi pada Matrix — standar untuk komunikasi terdesentralisasi terbuka.\n\nElement menempatkan Anda dalam kendali dengan membiarkan Anda memilih siapa yang menghost percakapan Anda. Dari aplikasi Element, Anda dapat memilih untuk menghost dengan cara yang berbeda:\n\n1. Dapatkan akun gratis pada server publik matrix.org\n2. Host sendiri akun Anda dengan menjalankan server pada perangkat keras Anda sendiri\n3. Mendaftar untuk akun di server khusus dengan hanya berlangganan platform hosting Element Matrix Services\n\nMengapa memilih Element?\n\nMILIKI DATA ANDA: Anda memutuskan di mana untuk menyimpan data dan pesan Anda. Anda memilikinya dan mengendalikannya, bukan perusahaan besar yang menambang data Anda atau memberikan akses ke pihak ketiga.\n\nPESAN DAN KOLABORASI TERBUKA: Anda dapat mengobrol dengan orang lain di jaringan Matrix, jika mereka menggunakan Element atau aplikasi Matrix lain, dan bahkan jika mereka menggunakan sistem perpesanan seperti Slack, IRC atau XMPP.\n\nSANGAT AMAN: Enkripsi ujung ke ujung yang nyata (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan penandatanganan silang untuk memverifikasi perangkat anggota obrolan.\n\nKOMUNIKASI LENGKAP: Perpesanan, panggilan suara dan video, pembagian file, pembagian layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.\n\nDI MANA PUN ANDA BERADA: Tetap berkomunikasi di mana pun Anda berada dengan riwayat pesan yang sepenuhnya disinkronkan di semua perangkat Anda dan di web di https://app.element.io/."; // String for App Store "store_short_description" = "Obrolan/VoIP terdesentralisasi aman"; @@ -121,14 +121,14 @@ "settings_crypto_device_key" = "\nKunci sesi:\n"; "settings_crypto_device_id" = "\nID Sesi: "; "settings_crypto_device_name" = "Nama sesi: "; -"settings_add_3pid_invalid_password_message" = "Kredential tidak valid"; +"settings_add_3pid_invalid_password_message" = "Kredential tidak absah"; "settings_confirm_password" = "Konfirmasi kata sandi"; "settings_new_password" = "Kata sandi baru"; "settings_old_password" = "Kata sandi lama"; "settings_third_party_notices" = "Pemberitahuan Pihak Ketiga"; "settings_privacy_policy" = "Kebijakan Privasi"; "settings_version" = "Versi %@"; -"settings_labs_e2e_encryption" = "Enkripsi Ujung-ke-Ujung"; +"settings_labs_e2e_encryption" = "Enkripsi Ujung ke Ujung"; "settings_contacts_phonebook_country" = "Negara buku telepon"; "settings_integrations_allow_button" = "Kelola integrasi"; "settings_enable_callkit" = "Panggilan yang diintegrasi"; @@ -558,7 +558,7 @@ "directory_searching_title" = "Mencari direktori…"; "room_details_advanced_room_id" = "ID Ruangan:"; "room_details_banned_users_section" = "Pengguna yang dicekal"; -"room_details_flair_invalid_id_prompt_title" = "Format tidak valid"; +"room_details_flair_invalid_id_prompt_title" = "Format tidak absah"; "room_details_history_section_prompt_title" = "Peringatan privasi"; "room_details_direct_chat" = "Pesan Langsung"; "room_details_mute_notifs" = "Bisukan notifikasi"; @@ -627,11 +627,11 @@ "secrets_recovery_with_passphrase_information_default" = "Akses riwayat pesan terenkripsi Anda dan identitas penandatanganan silang Anda untuk memverifikasi sesi lain dengan memasukkan Frasa Keamanan Anda."; "user_verification_session_details_additional_information_untrusted_other_user" = "Hingga pengguna ini memercayai sesi ini, pesan yang dikirim ke dan dari sesi ini akan diberi label peringatan. Atau, Anda dapat memverifikasinya secara manual."; "user_verification_session_details_information_untrusted_current_user" = "Verifikasi sesi ini untuk menandainya sebagai tepercaya & memberikan akses ke pesan terenkripsi:"; -"user_verification_sessions_list_information" = "Pesan dengan pengguna ini di ruangan ini dienkripsi secara ujung-ke-ujung dan tidak dapat dibaca oleh pihak ketiga."; +"user_verification_sessions_list_information" = "Pesan dengan pengguna ini di ruangan ini dienkripsi secara ujung ke ujung dan tidak dapat dibaca oleh pihak ketiga."; // User -"key_verification_verified_user_information" = "Pesan dengan pengguna ini dienkripsi secara ujung-ke-ujung dan tidak dapat dibaca oleh pihak ketiga."; +"key_verification_verified_user_information" = "Pesan dengan pengguna ini dienkripsi secara ujung ke ujung dan tidak dapat dibaca oleh pihak ketiga."; "key_verification_verified_this_session_information" = "Anda sekarang dapat membaca pesan aman di perangkat ini, dan pengguna lain akan tahu bahwa mereka dapat mempercayainya."; "key_verification_verified_new_session_information" = "Anda sekarang dapat membaca pesan aman di perangkat baru Anda, dan pengguna lain akan tahu bahwa mereka dapat mempercayainya."; "key_verification_verified_other_session_information" = "Anda sekarang dapat membaca pesan terenkripsi di sesi Anda yang lain, dan pengguna lain akan tahu bahwa mereka dapat mempercayainya."; @@ -643,7 +643,7 @@ "device_verification_self_verify_start_information" = "Gunakan sesi ini untuk memverifikasi sesi Anda yang baru, memberikan akses ke pesan terenkripsi."; "device_verification_start_use_legacy" = "Tidak ada yang muncul? Belum semua klien mendukung verifikasi interaktif. Gunakan verifikasi warisan."; "device_verification_incoming_description_2" = "Memverifikasi sesi ini akan menandainya sebagai tepercaya, dan juga menandai sesi Anda sebagai terpercaya kepada pengguna yang lain."; -"device_verification_incoming_description_1" = "Verifikasi sesi ini untuk menandainya sebagai tepercaya. Mempercayai sesi pengguna memberi Anda ketenangan pikiran ekstra saat menggunakan enkripsi ujung-ke-ujung."; +"device_verification_incoming_description_1" = "Verifikasi sesi ini untuk menandainya sebagai tepercaya. Mempercayai sesi pengguna memberi Anda ketenangan pikiran lebih saat menggunakan enkripsi ujung ke ujung."; "sign_out_key_backup_in_progress_alert_title" = "Pencadangan kunci sedang berlangsung. Jika Anda keluar sekarang, Anda akan kehilangan akses ke pesan terenkripsi Anda."; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Anda akan kehilangan akses ke pesan terenkripsi Anda kecuali jika Anda mencadangkan kunci Anda sebelum keluar."; "key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Kehilangan Kunci Keamanan Anda dapat menyiapkan yang baru di pengaturan."; @@ -672,7 +672,7 @@ "e2e_key_backup_wrong_version" = "Cadangan kunci pesan aman baru telah terdeteksi.\n\nJika ini bukan Anda, atur Frasa Keamanan baru di Pengaturan."; // Crypto -"e2e_enabling_on_app_update" = "%@ sekarang mendukung enkripsi ujung-ke-ujung tetapi Anda harus masuk lagi untuk mengaktifkannya.\n\nAnda dapat melakukannya sekarang atau nanti di pengaturan aplikasi."; +"e2e_enabling_on_app_update" = "%@ sekarang mendukung enkripsi ujung ke ujung tetapi Anda harus masuk lagi untuk mengaktifkannya.\n\nAnda dapat melakukannya sekarang atau nanti di pengaturan aplikasi."; // Crash report "no_voip" = "%@ sedang memanggil Anda tetapi %@ belum mendukung panggilan.\nAnda dapat mengabaikan notifikasi ini dan jawab panggilannya di perangkat yang lain atau menolak panggilannya."; @@ -757,8 +757,8 @@ "group_participants_remove_prompt_msg" = "Apakah Anda yakin untuk mengeluarkan %@ dari grup ini?"; "group_participants_leave_prompt_msg" = "Apakah Anda yakin untuk meninggalkan grup ini?"; "room_details_fail_to_update_room_direct" = "Gagal untuk memperbarui detail ruangan ini"; -"room_details_flair_invalid_id_prompt_msg" = "%@ bukan pengenal yang valid untuk sebuah komunitas"; -"room_details_addresses_invalid_address_prompt_msg" = "%@ bukan format yang valid untuk sebuah alias"; +"room_details_flair_invalid_id_prompt_msg" = "%@ bukan pengenal yang absah untuk sebuah komunitas"; +"room_details_addresses_invalid_address_prompt_msg" = "%@ bukan format yang absah untuk sebuah alias"; "room_details_history_section_members_only" = "Anggota saha (sejak opsi ini dipilih)"; "room_details_access_section_no_address_warning" = "Untuk menautkan ke ruangan itu harus memiliki alamat"; "voice_message_stop_locked_mode_recording" = "Ketuk pada rekaman Anda untuk berhenti atau dengarkan"; @@ -807,7 +807,7 @@ "widget_sticker_picker_no_stickerpacks_alert" = "Saat ini Anda tidak mengaktifkan paket stiker apa pun."; "call_already_displayed" = "Sudah ada panggilan yang sedang berlangsung."; "camera_unavailable" = "Kamera tidak tersedia di perangkat Anda"; -"network_offline_prompt" = "Koneksi internetnya terlihat offline."; +"network_offline_prompt" = "Koneksi internet sepertinya luring."; "group_participants_invite_another_user" = "Cari/undang dengan ID Pengguna atau Nama"; "group_invitation_format" = "%@ telah mengundang Anda untuk bergabung ke komunitas ini"; "room_notifs_settings_manage_notifications" = "Anda dapat mengelola notifikasi di %@"; @@ -820,7 +820,7 @@ "room_details_access_section_anyone_for_dm" = "Siapa saja yang tahu tautannya, termasuk tamu"; "room_details_access_section_anyone" = "Siapa saja yang tahu tautannya ruangan, termasuk tamu"; "room_details_access_section_anyone_apart_from_guest_for_dm" = "Siapa saja yang tahu linknya, selain dari tamu"; -"identity_server_settings_alert_error_invalid_identity_server" = "%@ bukan server identitas yang valid."; +"identity_server_settings_alert_error_invalid_identity_server" = "%@ bukan server identitas yang absah."; "identity_server_settings_alert_no_terms_title" = "Server identitas tidak mempunyai kebijakan layanan"; "security_settings_user_password_description" = "Konfirmasi identitas Anda dengan memasukkan kata sandi akun Matrix Anda"; "security_settings_secure_backup_info_valid" = "Sesi ini mencadangkan kunci Anda."; @@ -864,7 +864,7 @@ "room_predecessor_link" = "Ketuk di sini untuk melihat pesan lama."; "room_many_users_are_typing" = "%@, %@ & lainnya sedang mengetik…"; "room_two_users_are_typing" = "%@ & %@ sedang mengetik…"; -"room_participants_security_information_room_not_encrypted_for_dm" = "Pesan di sini tidak terenkripsi secara ujung-ke-ujung."; +"room_participants_security_information_room_not_encrypted_for_dm" = "Pesan di sini tidak terenkripsi secara ujung ke ujung."; "room_participants_action_unignore" = "Tampilkan semua pesan dari penguna ini"; "room_participants_action_ignore" = "Sembunyikan semua pesan dari pengguna ini"; "find_your_contacts_title" = "Mulai dengan mendaftar kontak Anda"; @@ -1021,7 +1021,7 @@ "room_creation_private_room" = "Obrolan ini privat"; "social_login_button_title_sign_up" = "Daftar dengan %@"; "social_login_button_title_sign_in" = "Masuk dengan %@"; -"auth_autodiscover_invalid_response" = "Respons penemuan homeserver tidak valid"; +"auth_autodiscover_invalid_response" = "Respons penemuan homeserver tidak absah"; "pin_protection_settings_section_header_with_biometrics" = "PIN & %@"; "service_terms_modal_table_header_integration_manager" = "SYARAT PENGELOLA INTEGRASI"; "service_terms_modal_table_header_identity_server" = "SYARAT SERVER IDENTITAS"; @@ -1033,7 +1033,7 @@ "event_formatter_call_incoming_voice" = "Panggilan suara masuk"; // Room Notification Settings -"room_notifs_settings_notify_me_for" = "Beritahu saya untuk"; +"room_notifs_settings_notify_me_for" = "Beri tahu saya untuk"; "security_settings_secure_backup_restore" = "Pulihkan dari Cadangan"; "settings_contacts_enable_sync" = "Cari kontak Anda"; "settings_show_url_previews" = "Tampilkan tampilan website"; @@ -1041,7 +1041,7 @@ "settings_messages_containing_display_name" = "Nama tampilan saya"; "settings_encrypted_group_messages" = "Pesan grup terenkripsi"; "settings_encrypted_direct_messages" = "Pesan langsung terenkripsi"; -"settings_notify_me_for" = "Beritahu saya untuk"; +"settings_notify_me_for" = "Beri tahu saya untuk"; "settings_mentions_and_keywords" = "Sebutan dan Keyword"; "find_your_contacts_button_title" = "Cari kontak Anda"; "call_incoming_voice" = "Panggilan masuk…"; @@ -1111,7 +1111,7 @@ "key_verification_this_session_title" = "Verifikasi sesi ini"; "key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Masukkan Kunci Keamanan"; "key_backup_recover_invalid_recovery_key_title" = "Kunci Keamanan Tidak Cocok"; -"key_backup_recover_invalid_passphrase_title" = "Frasa Sandi Tidak Benar"; +"key_backup_recover_invalid_passphrase_title" = "Frasa Keamanan Tidak Benar"; "key_backup_setup_success_from_recovery_key_make_copy_action" = "Buat sebuah Salinan"; "key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Simpan Kunci Keamanan"; "key_backup_setup_passphrase_confirm_passphrase_invalid" = "frasa tidak cocok"; @@ -1164,7 +1164,7 @@ "room_details_copy_room_address" = "Salin Alamat Ruangan"; "room_details_copy_room_id" = "Salin ID Ruangan"; "room_details_addresses_disable_main_address_prompt_title" = "Peringatan alamat utama"; -"room_details_addresses_invalid_address_prompt_title" = "Format alias tidak valid"; +"room_details_addresses_invalid_address_prompt_title" = "Format alias tidak absah"; "room_details_new_address" = "Tambahkan alamat baru"; "identity_server_settings_alert_disconnect_title" = "Putuskan server identitas"; "identity_server_settings_alert_change_title" = "Ubah server identitas"; @@ -1223,7 +1223,7 @@ "room_participants_action_start_new_chat" = "Mulai obrolan baru"; "room_participants_action_leave" = "Tinggalkan ruangan ini"; "room_participants_filter_room_members" = "Filter anggota ruangan"; -"contacts_user_directory_offline_section" = "DIREKTORI PENGGUNA (offline)"; +"contacts_user_directory_offline_section" = "DIREKTORI PENGGUNA (luring)"; "contacts_address_book_permission_denied_alert_title" = "Kontak dinonaktifkan"; "contacts_address_book_no_contact" = "Tidak ada kontak lokal"; "contacts_address_book_matrix_users_toggle" = "Hanya pengguna Matrix"; @@ -1421,16 +1421,16 @@ "widget_integration_must_be_in_room" = "Anda tidak berada di ruangan ini."; "settings_devices_description" = "Nama publik sesi dapat dilihat oleh orang yang berkomunikasi dengan Anda"; "settings_key_backup_delete_confirmation_prompt_msg" = "Apakah Anda yakin? Anda akan kehilangan pesan terenkripsi jika kunci Anda tidak dicadangkan secara benar."; -"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Cadangan memiliki tanda tangan yang tidak valid dari %@"; -"settings_key_backup_info_trust_signature_invalid_device_verified" = "Cadangan mempunyai tanda tangan yang tidak valid dari %@"; -"settings_key_backup_info_trust_signature_valid_device_verified" = "Cadangan mempunyai tanda tangan yang valid dari %@"; -"settings_key_backup_info_trust_signature_valid" = "Cadangan mempunyai tanda tangan yang valid dari sesi ini"; +"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Cadangan memiliki tanda tangan yang tidak absah dari %@"; +"settings_key_backup_info_trust_signature_invalid_device_verified" = "Cadangan mempunyai tanda tangan yang tidak absah dari %@"; +"settings_key_backup_info_trust_signature_valid_device_verified" = "Cadangan mempunyai tanda tangan yang absah dari %@"; +"settings_key_backup_info_trust_signature_valid" = "Cadangan mempunyai tanda tangan yang absah dari sesi ini"; "settings_key_backup_info_trust_signature_unknown" = "Cadangan mempunyai tanda tangan dari sesi dengan ID: %@"; "settings_key_backup_info_not_valid" = "Sesi ini tidak mencadangkan kunci Anda, tetapi Anda memiliki cadangan yang ada yang dapat Anda pulihkan dan tambahkan untuk selanjutnya."; "settings_key_backup_info_valid" = "Sesi ini mencadangkan kunci Anda."; "settings_key_backup_info_signout_warning" = "Cadangkan kunci Anda sebelum keluar untuk mencegah kehilangannya."; "settings_key_backup_info_none" = "Kunci Anda tidak dicadangkan dari sesi ini."; -"settings_key_backup_info" = "Pesan terenkripsi diamankan dengan enkripsi ujung-ke-ujung. Hanya Anda dan penerima punya kuncinya untuk membaca pesannya."; +"settings_key_backup_info" = "Pesan terenkripsi diamankan dengan enkripsi ujung ke ujung. Hanya Anda dan penerima punya kuncinya untuk membaca pesannya."; "settings_labs_e2e_encryption_prompt_message" = "Untuk menyelesaikan enkripsi Anda harus masuk lagi."; "settings_contacts_enable_sync_description" = "Ini akan menggunakan server identitas Anda untuk menghubung Anda dengan kontak Anda, dan membantunya untuk menemukan Anda."; "settings_show_url_previews_description" = "Pratinjau akan ditampilkan di ruangan yang tidak dienkripsi."; @@ -1467,7 +1467,7 @@ // Chat "room_slide_to_end_group_call" = "Geser untuk mengakhiri panggilan untuk semuanya"; -"room_participants_security_information_room_not_encrypted" = "Pesan di ruangan ini tidak dienkripsi secara ujung-ke-ujung."; +"room_participants_security_information_room_not_encrypted" = "Pesan di ruangan ini tidak dienkripsi secara ujung ke ujung."; "room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Tidak ada server identitas yang diatur, jadi Anda tidak dapat mengobrol dengan sebuah kontak menggunakan email."; "room_participants_invite_malformed_id" = "ID cacat. Seharusnya sebuah alamat email atau ID Matrix seperti '@pengguna:domain'"; "room_participants_invite_another_user" = "Cari/Undang dengan ID Pengguna, Nama atau email"; @@ -1525,11 +1525,11 @@ "settings_add_3pid_password_message" = "Untuk melanjutkan, mohon masukkan kata sandi akun Matrix Anda"; "settings_send_crash_report" = "Kirim crash & data penggunaan anonim"; "secure_key_backup_setup_cancel_alert_message" = "Jika Anda membatalkan sekarang, Anda mungkin kehilangan pesan & data terenkripsi jika Anda kehilangan akses ke login Anda.\n\nAnda juga dapat mengatur Cadangan Aman & kelola kunci Anda di Pengaturan."; -"room_participants_security_information_room_encrypted" = "Pesan di ruangan ini dienkripsi secara ujung-ke-ujung.\n\nPesan Anda diamankan dengan kunci dan hanya Anda dan penerima punya kunci uniknya untuk membukanya."; +"room_participants_security_information_room_encrypted" = "Pesan di ruangan ini dienkripsi secara ujung ke ujung.\n\nPesan Anda diamankan dengan kunci dan hanya Anda dan penerima punya kunci uniknya untuk membukanya."; "settings_callkit_info" = "Terima panggilan masuk di layar kunci Anda. Lihat panggilan %@ Anda di riwayat panggilan sistem. Jika iCloud diaktifkan, riwayat panggilannya akan dibagikan dengan Apple."; "settings_three_pids_management_information_part1" = "Kelola alamat email dan nomor telepon apa saja Anda dapat gunakan untuk masuk atau memulihkan akun Anda di sini. Atur siapa saja yang dapat menemukan Anda di "; -"settings_sign_out_e2e_warn" = "Anda akan kehilangan kunci enkripsi ujung-ke-ujung Anda. Ini berarti Anda tidak akan lagi dapat membaca pesan lama di ruangan terenkripsi di perangkat ini."; -"room_participants_security_information_room_encrypted_for_dm" = "Pesan di pesan langsung ini dienkripsi secara ujung-ke-ujung.\n\nPesan Anda diamankan dengan kunci dan hanya Anda dan penerima punya kunci uniknya untuk membukanya."; +"settings_sign_out_e2e_warn" = "Anda akan kehilangan kunci enkripsi ujung ke ujung Anda. Ini berarti Anda tidak akan lagi dapat membaca pesan lama di ruangan terenkripsi di perangkat ini."; +"room_participants_security_information_room_encrypted_for_dm" = "Pesan di pesan langsung ini dienkripsi secara ujung ke ujung.\n\nPesan Anda diamankan dengan kunci dan hanya Anda dan penerima punya kunci uniknya untuk membukanya."; "auth_softlogout_clear_data_sign_out_msg" = "Apakah Anda yakin ingin menghapus semua data yang saat ini tersimpan di perangkat ini? Masuk lagi untuk mengakses data dan pesan akun Anda."; "auth_softlogout_recover_encryption_keys" = "Masuk untuk memulihkan kunci enkripsi yang disimpan secara eksklusif di perangkat ini. Anda membutuhkannya untuk membaca semua pesan aman Anda di perangkat apa saja."; "version_check_modal_subtitle_deprecated" = "Kami telah berupaya meningkatkan %@ untuk pengalaman yang lebih cepat dan lebih halus. Sayangnya versi iOS Anda saat ini tidak kompatibel dengan beberapa perbaikan tersebut dan tidak akan didukung lagi.\nKami menyarankan Anda untuk meningkatkan sistem operasi Anda untuk menggunakan %@ secara maksimal."; @@ -1539,17 +1539,17 @@ // Success from passphrase "key_backup_setup_success_from_passphrase_info" = "Kunci Anda sedang dicadangkan.\n\nKunci Keamanan Anda adalah jaring pengaman — Anda dapat menggunakannya untuk memulihkan akses ke pesan terenkripsi jika Anda lupa frasa sandi.\n\nSimpan Kunci Keamanan Anda di suatu tempat yang sangat aman, seperti pengelola kata sandi (atau brankas)."; "key_backup_setup_passphrase_info" = "Kami akan menyimpan salinan terenkripsi dari kunci Anda di server kami. Lindungi cadangan Anda dengan frasa agar tetap aman.\n\nUntuk keamanan maksimum, ini harus berbeda dari kata sandi akun Matrix Anda."; -"key_backup_setup_intro_info" = "Pesan di ruang terenkripsi diamankan dengan enkripsi ujung-ke-ujung. Hanya Anda dan penerima yang memiliki kunci untuk membaca pesan ini.\n\nCadangkan kunci Anda dengan aman untuk menghindari kehilangannya."; +"key_backup_setup_intro_info" = "Pesan di ruang terenkripsi diamankan dengan enkripsi ujung ke ujung. Hanya Anda dan penerima yang memiliki kunci untuk membaca pesan ini.\n\nCadangkan kunci Anda dengan aman untuk menghindari kehilangannya."; "deactivate_account_informations_part5" = "Jika Anda ingin kami melupakan pesan Anda, silakan centang kotak di bawah ini\n\nVisibilitas pesan di Matrix mirip dengan email. Kami melupakan pesan Anda berarti bahwa pesan yang telah Anda kirim tidak akan dibagikan dengan pengguna baru atau tidak terdaftar, tetapi pengguna terdaftar yang sudah memiliki akses ke pesan ini akan tetap memiliki akses ke salinannya."; "deactivate_account_informations_part1" = "Ini akan membuat akun Anda tidak dapat digunakan secara permanen. Anda tidak akan dapat masuk, dan tidak seorang pun dapat mendaftarkan ulang ID pengguna yang sama. Ini akan menyebabkan akun Anda meninggalkan semua ruangan yang diikutinya, dan akan menghapus detail akun Anda dari server identitas Anda. "; -"e2e_need_log_in_again" = "Anda harus masuk kembali untuk membuat kunci enkripsi ujung-ke-ujung untuk sesi ini dan mengirimkan kunci publik ke homeserver Anda.\nIni hanya dilakukan sekali saja; maaf untuk ketidaknyamanannya."; +"e2e_need_log_in_again" = "Anda harus masuk kembali untuk membuat kunci enkripsi ujung ke ujung untuk sesi ini dan mengirimkan kunci publik ke homeserver Anda.\nIni hanya dilakukan sekali saja; maaf untuk ketidaknyamanan."; "call_no_stun_server_error_message_2" = "Atau, Anda dapat mencoba menggunakan server publik di %@, tetapi ini tidak akan dapat diandalkan, dan alamat IP Anda akan dibagikan dengan server tersebut. Anda juga dapat mengelola ini di Pengaturan"; "identity_server_settings_alert_disconnect_still_sharing_3pid" = "Anda masih membagikan data personal Anda di server identitas %@.\n\nKami mensarankan Anda untuk menghapus alamat email dan nomor telepon Anda dari server identitasnya sebelum memutuskan hubungannya."; "security_settings_crosssigning_info_trusted" = "Penandatanganan silang diaktifkan. Anda dapat mempercayai pengguna lain dan sesi lain Anda berbasis penandatanganan silang tetapi Anda tidak dapat menandatangani sesi ini karena tidak memiliki kunci privat penandatanganan silang. Keamanan lengkap sesi ini."; "settings_discovery_three_pids_management_information_part1" = "Kelola alamat email atau nomor telepon apa saja yang pengguna lain dapat menggunakan untuk menemukan Anda dan menggunakannya untuk mengundang Anda ke ruangan. Tambahkan atau hapus alamat email atau nomor telepon dari daftar ini di "; "room_preview_unlinked_email_warning" = "Undangan ini telah dikirim ke %@, yang tidak diasosiasikan dengan akun ini. Anda mungkin ingin masuk ke akun yang lain, atau tambahkan email ini ke akun Anda."; "unknown_devices_alert" = "Ruangan ini berisi sesi tidak dikenal yang belum diverifikasi.\nIni berarti tidak ada jaminan bahwa sesi tersebut adalah milik pengguna yang mereka klaim.\nKami menyarankan Anda memverifikasinya untuk setiap sesi sebelum melanjutkan, tetapi Anda dapat mengirim ulang pesan tanpa memverifikasi jika Anda ingin."; -"room_warning_about_encryption" = "Enkripsi ujung-ke-ujung masih dalam beta dan mungkin tidak dapat diandalkan.\n\nAnda seharusnya tidak mempercayainya dulu untuk mengamankan data.\n\nPerangkat masih belum dapat mendekripsi riwayat sebelum mereka bergabung ke ruangannya.\n\nPesan terenkripsi masih belum terlihat di klen yang belum mengimplementasikan enkripsi."; +"room_warning_about_encryption" = "Enkripsi ujung ke ujung masih dalam beta dan mungkin tidak dapat diandalkan.\n\nAnda seharusnya tidak mempercayainya dulu untuk mengamankan data.\n\nPerangkat masih belum dapat mendekripsi riwayat sebelum mereka bergabung ke ruangannya.\n\nPesan terenkripsi masih belum terlihat di klien yang belum mengimplementasikan enkripsi."; "auth_add_email_and_phone_warning" = "Pendaftaran dengan email dan nomor telepon sekaligus belum didukung sampai API-nya sudah ada. Hanya nomor telepon yang akan diperhitungkan. Anda dapat menambahkan email Anda di profil Anda di pengaturan."; "auth_reset_password_success_message" = "Kata sandi akun Matrix Anda telah diatur ulang.\n\nAnda telah dikeluarkan dari semua sesi dan tidak akan menerima lagi notifikasi push. Untuk mengaktifkan ulang notifikasi, masuk ulang di setiap perangkat."; "spaces_add_rooms_coming_soon_title" = "Penambahan ruangan akan segera datang"; @@ -1724,7 +1724,7 @@ "settings_enable_room_message_bubbles" = "Gelembung pesan"; "onboarding_splash_page_4_message" = "Element juga bagus untuk tempat kerja. Terpercayai oleh organisasi paling aman di dunia."; "onboarding_splash_page_4_title_no_pun" = "Perpesanan untuk tim Anda."; -"onboarding_splash_page_3_message" = "Terenkripsi secara ujung-ke-ujung dan tidak memerlukan nomor telepon. Tidak ada iklan atau penambangan data."; +"onboarding_splash_page_3_message" = "Terenkripsi secara ujung ke ujung dan tidak memerlukan nomor telepon. Tanpa iklan atau penambangan data."; "onboarding_splash_page_3_title" = "Perpesanan aman."; "onboarding_splash_page_2_message" = "Pilihlah di mana percakapan Anda disimpan, memberikan Anda kendali dan kebebasan. Terhubung via Matrix."; "onboarding_splash_page_2_title" = "Anda dalam kendali."; @@ -1861,16 +1861,16 @@ "login_mobile_device" = "Ponsel"; "login_error_forgot_password_is_not_supported" = "Lupa kata sandi saat ini belum didukung"; "register_error_title" = "Pendaftaran Gagal"; -"login_invalid_param" = "Parameter tidak valid"; +"login_invalid_param" = "Parameter tidak absah"; "login_leave_fallback" = "Batalkan"; "login_use_fallback" = "Gunakan halaman fallback"; "login_error_login_email_not_yet" = "Tautan email yang belum diklik"; "login_error_user_in_use" = "Nama pengguna ini sudah dipakai"; "login_error_limit_exceeded" = "Terlalu banyak permintaan yang dikirim"; -"login_error_not_json" = "Tidak mengandung JSON yang valid"; +"login_error_not_json" = "Tidak mengandung JSON yang absah"; "login_error_unknown_token" = "Token akses yang ditentukan tidak diketahui"; "login_error_bad_json" = "JSON cacat"; -"login_error_forbidden" = "Nama pengguna/kata sandi tidak valid"; +"login_error_forbidden" = "Nama pengguna/kata sandi tidak absah"; "login_error_registration_is_not_supported" = "Pendaftaran saat ini tidak didukung"; "login_error_do_not_support_login_flows" = "Saat ini kami tidak mendukung salah satu atau semua alur masuk yang ditentukan oleh homeserver ini"; "login_error_no_login_flow" = "Kami gagal untuk menerima informasi otentikasi dari homeserver ini"; @@ -1931,15 +1931,15 @@ "call_connecting" = "Menghubungkan…"; // gcm section -"notification_settings_notify_all_other" = "Beritahu untuk semua pesan/ruangan lainnya"; +"notification_settings_notify_all_other" = "Beri tahu untuk semua pesan/ruangan lainnya"; "notification_settings_by_default" = "Secara default..."; -"notification_settings_suppress_from_bots" = "Jangan beritahu saya tentang notifikasi dari bot"; -"notification_settings_receive_a_call" = "Beritahu saya ketika saya menerima panggilan"; -"notification_settings_people_join_leave_rooms" = "Beritahu saya ketika ada orang bergabung atau meninggalkan ruangan"; -"notification_settings_invite_to_a_new_room" = "Beritahu saya ketika saya diundang ke ruangan baru"; -"notification_settings_just_sent_to_me" = "Beritahu saya dengan suara tentang pesan yang baru saja dikirim ke saya"; -"notification_settings_contain_my_display_name" = "Beritahu saya dengan suara tentang pesan yang berisi nama tampilan saya"; -"notification_settings_contain_my_user_name" = "Beritahu saya dengan suara tentang pesan yang berisi nama pengguna saya"; +"notification_settings_suppress_from_bots" = "Jangan beri tahu saya tentang notifikasi dari bot"; +"notification_settings_receive_a_call" = "Beri tahu saya ketika saya menerima panggilan"; +"notification_settings_people_join_leave_rooms" = "Beri tahu saya ketika ada orang bergabung atau meninggalkan ruangan"; +"notification_settings_invite_to_a_new_room" = "Beri tahu saya ketika saya diundang ke ruangan baru"; +"notification_settings_just_sent_to_me" = "Beri tahu saya dengan suara tentang pesan yang baru saja dikirim ke saya"; +"notification_settings_contain_my_display_name" = "Beri tahu saya dengan suara tentang pesan yang berisi nama tampilan saya"; +"notification_settings_contain_my_user_name" = "Beri tahu saya dengan suara tentang pesan yang berisi nama pengguna saya"; "notification_settings_other_alerts" = "Pemberitahuan Lainnya"; "notification_settings_select_room" = "Pilih sebuah ruangan"; "notification_settings_sender_hint" = "@pengguna:domain.com"; @@ -1948,8 +1948,8 @@ "notification_settings_custom_sound" = "Suara kustom"; "notification_settings_highlight" = "Sorotan"; "notification_settings_word_to_match" = "kata untuk dicocokkan"; -"notification_settings_never_notify" = "Jangan diberitahu"; -"notification_settings_always_notify" = "Selalu diberitahu"; +"notification_settings_never_notify" = "Jangan beri tahu"; +"notification_settings_always_notify" = "Selalu beri tahu"; "notification_settings_per_word_info" = "Kata-kata tidak cocok dengan huruf besar-kecil, dan mungkin menyertakan karakter pengganti *. Jadi:\nfoo cocok dengan string foo yang dikelilingi oleh pembatas kata (misalnya tanda baca dan spasi atau awal/akhir baris).\nfoo* cocok dengan kata apa pun yang dimulai foo.\n*foo* cocok dengan kata apa pun yang menyertakan 3 huruf foo."; "notification_settings_per_word_notifications" = "Notifikasi per kata"; "notification_settings_global_info" = "Pengaturan notifikasi disimpan ke akun pengguna Anda dan dibagikan di antara semua client yang mendukungnya (termasuk pemberitahuan desktop).\n\nAturan diterapkan secara berurutan; aturan pertama yang cocok menentukan hasil untuk pesan.\nJadi: Notifikasi per kata lebih penting daripada notifikasi per ruangan yang lebih penting daripada notifikasi per pengirim.\nUntuk beberapa aturan dengan jenis yang sama, yang pertama dalam daftar yang cocok akan diprioritaskan."; @@ -2025,7 +2025,7 @@ // button names "notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "Anda membuat sejarah pesan di masa mendatang dapat dilihat oleh semuanya, sejak mereka bergabung."; "notice_room_history_visible_to_members_from_joined_point_by_you" = "Anda membuat sejarah ruangan di masa mendatang dapat dilihat oleh semua anggota ruang, sejak mereka bergabung."; -"notice_encryption_enabled_unknown_algorithm_by_you" = "Anda mengaktifkan enkripsi ujung-ke-ujung (algoritma %@ tidak dikenal)."; +"notice_encryption_enabled_unknown_algorithm_by_you" = "Anda mengaktifkan enkripsi ujung ke ujung (algoritma %@ tidak dikenal)."; "notice_room_third_party_revoked_invite" = "%@ menghilangkan undangannya %@ untuk bergabung ke ruangan ini"; "notice_room_third_party_revoked_invite_by_you" = "Anda menghilangkan undangannya %@ untuk bergabung ke ruangan ini"; "account_email_validation_error" = "Tidak dapat memverifikasi alamat email. Silakan cek email Anda dan tekan tautannya yang ada. Setelah selesai, tekan lanjut"; @@ -2038,7 +2038,7 @@ "notice_room_history_visible_to_members_by_you" = "Anda membuat sejarah ruangan di masa mendatang dapat dilihat oleh semua anggota ruangan."; "notice_room_history_visible_to_anyone_by_you" = "Anda membuat sejarah ruangan di masa mendatang dapat dilihat oleh siapa saja."; "notice_redaction_by_you" = "Anda menghapus sebuah peristiwa (id: %@)"; -"notice_encryption_enabled_ok_by_you" = "Anda mengaktifkan enkripsi ujung-ke-ujung."; +"notice_encryption_enabled_ok_by_you" = "Anda mengaktifkan enkripsi ujung ke ujung."; "notice_room_created_by_you_for_dm" = "Anda bergabung."; "notice_room_created_by_you" = "Anda membuat dan mengatur ruangan ini."; "notice_profile_change_redacted_by_you" = "Anda memperbarui profil Anda %@"; @@ -2135,7 +2135,7 @@ "error_common_message" = "Sebuah kesalahan terjadi. Coba lagi nanti."; "error" = "Gagal"; "unsent" = "Belum Terkirim"; -"offline" = "offline"; +"offline" = "luring"; // Others "user_id_title" = "ID Pangguna:"; @@ -2225,10 +2225,10 @@ // Room creation "room_creation_name_title" = "Nama ruangan:"; "account_error_push_not_allowed" = "Notifikasi tidak diizinkan"; -"account_error_msisdn_wrong_description" = "Ini sepertinya bukan nomor telepon yang valid"; -"account_error_msisdn_wrong_title" = "Nomor Telepon Tidak Valid"; -"account_error_email_wrong_description" = "Ini sepertinya bukan alamat email yang valid"; -"account_error_email_wrong_title" = "Alamat Email Tidak Valid"; +"account_error_msisdn_wrong_description" = "Ini sepertinya bukan nomor telepon yang Absah"; +"account_error_msisdn_wrong_title" = "Nomor Telepon Tidak Absah"; +"account_error_email_wrong_description" = "Ini sepertinya bukan alamat email yang absah"; +"account_error_email_wrong_title" = "Alamat Email Tidak Absah"; "account_error_matrix_session_is_not_opened" = "Sesi Matrix tidak dibuka"; "account_error_picture_change_failed" = "Penggantian gambar gagal"; "account_error_display_name_change_failed" = "Penggantian nama tampilan gagal"; @@ -2282,7 +2282,7 @@ // Devices "device_details_title" = "Informasi sesi\n"; "notification_settings_room_rule_title" = "Ruangan: '%@'"; -"settings_enter_validation_token_for" = "Masukkan token validasi untuk %@:"; +"settings_enter_validation_token_for" = "Masukkan token absah untuk %@:"; "settings_enable_push_notifications" = "Aktifkan notifikasi push"; "settings_enable_inapp_notifications" = "Aktifkan notifikasi dalam aplikasi"; @@ -2311,14 +2311,14 @@ "notice_redaction" = "%@ menghapus sebuah peristiwa (id: %@)"; "notice_feedback" = "Peristiwa umpan balik (id: %@): %@"; "notice_unsupported_attachment" = "Lampiran yang tidak didukung: %@"; -"notice_invalid_attachment" = "lampiran tidak valid"; +"notice_invalid_attachment" = "lampiran tidak absah"; "notice_file_attachment" = "lampiran file"; "notice_location_attachment" = "lampiran lokasi"; "notice_video_attachment" = "lampiran video"; "notice_audio_attachment" = "lampiran audio"; "notice_image_attachment" = "lampiran gambar"; -"notice_encryption_enabled_unknown_algorithm" = "%1$@ mengaktifkan enkripsi ujung-ke-ujung (algoritma %2$@ tidak dikenal)."; -"notice_encryption_enabled_ok" = "%@ mengaktifkan enkripsi ujung-ke-ujung."; +"notice_encryption_enabled_unknown_algorithm" = "%1$@ mengaktifkan enkripsi ujung ke ujung (algoritma %2$@ tidak dikenal)."; +"notice_encryption_enabled_ok" = "%@ mengaktifkan enkripsi ujung ke ujung."; "notice_encrypted_message" = "Pesan terenkripsi"; "notice_room_related_groups" = "Grup yang terkait dengan ruangan ini adalah: %@"; "notice_room_aliases_for_dm" = "Aliasnya adalah: %@"; @@ -2358,7 +2358,7 @@ "spaces_creation_invite_by_username_message" = "Anda juga dapat mengundang mereka nanti."; "spaces_creation_invite_by_username_title" = "Undang tim Anda"; "spaces_creation_invite_by_username" = "Undang dengan nama pengguna"; -"spaces_creation_add_rooms_message" = "Space ini hanya untuk Anda, tidak akan diberitahukan kepada siapa saja. Anda dapat menambahkan lagi nanti."; +"spaces_creation_add_rooms_message" = "Space ini hanya untuk Anda, tidak akan diberi tahu kepada siapa pun. Anda dapat menambahkan lagi nanti."; "spaces_creation_add_rooms_title" = "Apa yang Anda ingin tambahkan?"; "spaces_creation_sharing_type_me_and_teammates_detail" = "Space privat untuk Anda & tim Anda"; "spaces_creation_sharing_type_me_and_teammates_title" = "Saya dan tim saya"; @@ -2380,7 +2380,7 @@ "spaces_creation_private_space_title" = "Space privat Anda"; "spaces_creation_public_space_title" = "Space publik Anda"; "spaces_creation_address_already_exists" = "%@\nsudah ada"; -"spaces_creation_address_invalid_characters" = "%@\nmemiliki karakter yang tidak valid"; +"spaces_creation_address_invalid_characters" = "%@\nmemiliki karakter yang tidak absah"; "spaces_creation_address_default_message" = "Space Anda dapat ditampilkan di\n%@"; "spaces_creation_empty_room_name_error" = "Nama dibutuhkan"; "spaces_creation_address" = "Alamat"; @@ -2573,7 +2573,7 @@ "authentication_terms_policy_url_error" = "Tidak dapat menemukan kebijakan yang dipilih. Mohon coba lagi nanti."; "authentication_terms_message" = "Mohon baca ketentuan dan kebijakan %@"; "authentication_terms_title" = "Kebijakan privasi"; -"authentication_verify_msisdn_invalid_phone_number" = "Nomor telepon tidak valid"; +"authentication_verify_msisdn_invalid_phone_number" = "Nomor telepon tidak absah"; "authentication_verify_msisdn_waiting_button" = "Kirim ulang kode"; /* The placeholder will show the phone number that was entered. */ "authentication_verify_msisdn_waiting_message" = "Sebuah kode terkirim ke %@"; @@ -2674,7 +2674,7 @@ "room_invites_empty_view_title" = "Belum ada yang baru."; "all_chats_onboarding_try_it" = "Coba"; "all_chats_onboarding_title" = "Apa yang baru"; -"all_chats_onboarding_page_message3" = "Ketuk profil Anda untuk memberitahukan kami bagaimana menurut Anda."; +"all_chats_onboarding_page_message3" = "Ketuk profil Anda untuk memberi tahu kami bagaimana menurut Anda."; "all_chats_onboarding_page_title3" = "Berikan Masukan"; "all_chats_onboarding_page_message2" = "Akses Space Anda (di kiri bawah) dengan lebih cepat dan lebih mudah dari sebelumnya."; "all_chats_onboarding_page_title2" = "Akses Space"; @@ -2696,7 +2696,7 @@ "device_name_mobile" = "%@ Ponsel"; "device_name_web" = "%@ Web"; "device_name_desktop" = "%@ Desktop"; -"user_session_item_details" = "%@ · Aktivitas terakhir %@"; +"user_session_item_details" = "%1$@ · %2$@"; // First item is client name and second item is session display name "user_session_name" = "%@: %@"; @@ -2709,8 +2709,126 @@ "user_session_verified_short" = "Terverifikasi"; "user_session_unverified" = "Sesi belum diverifikasi"; "user_session_verified" = "Sesi terverifikasi"; -"user_sessions_overview_current_session_section_title" = "SESI SAAT INI"; +"user_sessions_overview_current_session_section_title" = "Sesi saat ini"; "user_sessions_overview_other_sessions_section_info" = "Untuk keamanan yang terbaik, verifikasi sesi Anda dan keluarkan dari sesi yang Anda tidak kenal atau tidak digunakan lagi."; -"user_sessions_overview_other_sessions_section_title" = "SESI LAINNYA"; +"user_sessions_overview_other_sessions_section_title" = "Sesi lainnya"; "settings_labs_enable_new_app_layout" = "Tata Letak Aplikasi Baru"; "room_first_message_placeholder" = "Kirim pesan pertama Anda…"; +"room_event_encryption_info_key_authenticity_not_guaranteed" = "Keaslian pesan terenkripsi ini tidak dapat dijamin pada perangkat ini."; +"user_session_overview_session_details_button_title" = "Detail sesi"; +"user_session_overview_session_title" = "Sesi"; +"user_session_overview_current_session_title" = "Sesi saat ini"; +"user_session_details_application_url" = "URL"; +"user_session_details_application_version" = "Versi"; +"user_session_details_application_name" = "Nama"; +"user_session_details_device_os" = "Sistem Operasi"; +"user_session_details_device_browser" = "Peramban"; +"user_session_details_device_model" = "Model"; +"user_session_details_device_ip_location" = "Lokasi IP"; +"user_session_details_device_ip_address" = "Alamat IP"; +"user_session_details_session_section_footer" = "Salin data apa pun dengan mengetuk dan menahan."; +"user_session_details_session_id" = "ID sesi"; +"user_session_details_session_name" = "Nama sesi"; +"user_session_details_device_section_header" = "Perangkat"; +"user_session_details_application_section_header" = "Aplikasi"; +"user_session_details_session_section_header" = "Sesi"; +"user_session_details_title" = "Detail sesi"; +"user_session_push_notifications_message" = "Ketika ini diaktifkan, sesi ini akan menerima notifikasi dorongan."; +"user_session_push_notifications" = "Notifikasi dorongan"; +"user_sessions_view_all_action" = "Tampilkan semua (%d)"; +"user_sessions_overview_security_recommendations_inactive_info" = "Pertimbangkan untuk mengeluarkan sesi lama (90 hari atau lebih) yang Anda tidak gunakan lagi."; +"user_sessions_overview_security_recommendations_inactive_title" = "Sesi yang tidak aktif"; +"user_sessions_overview_security_recommendations_unverified_info" = "Verifikasi atau keluarkan sesi yang belum diverifikasi."; +"user_sessions_overview_security_recommendations_unverified_title" = "Sesi yang belum diverifikasi"; +"user_sessions_overview_security_recommendations_section_info" = "Tingkatkan keamanan akun Anda dengan mengikuti saran berikut."; +"user_sessions_overview_security_recommendations_section_title" = "Saran keamanan"; +"all_chats_user_menu_accessibility_label" = "Menu pengguna"; +"settings_labs_enable_new_client_info_feature" = "Rekam nama, versi, dan URL klien untuk dapat mengenal sesi dengan lebih muda dalam pengelola sesi"; +"settings_labs_enable_new_session_manager" = "Pengelola sesi baru"; +"device_type_name_unknown" = "Tidak diketahui"; +"device_type_name_mobile" = "Ponsel"; +"device_type_name_web" = "Web"; +"device_type_name_desktop" = "Desktop"; +"user_inactive_session_item_with_date" = "Tidak aktif selama 90+ hari (%@)"; +"user_inactive_session_item" = "Tidak aktif selama 90+ hari"; +"user_other_session_unverified_sessions_header_subtitle" = "Verifikasi sesi Anda untuk perpesanan aman yang terbaik atau keluarkan sesi yang Anda tidak kenal atau gunakan lagi."; +"user_other_session_security_recommendation_title" = "Saran keamanan"; +"user_sessions_overview_link_device" = "Tautkan sebuah perangkat"; + +// MARK: User sessions management + +// Parameter is the application display name (e.g. "Element") +"user_sessions_default_session_display_name" = "%@ iOS"; + +// MARK: Sign out warning + +"sign_out" = "Keluar dari akun"; +"sign_out_confirmation_message" = "Apakah Anda yakin ingin keluar dari akun Anda?"; +"manage_session_rename" = "Ubah nama sesi"; +"authentication_qr_login_failure_retry" = "Coba lagi"; +"authentication_qr_login_failure_request_timed_out" = "Penautan tidak selesai dalam waktu yang diperlukan."; +"authentication_qr_login_failure_request_denied" = "Permintaan ditolak di perangkat yang lain."; +"authentication_qr_login_failure_invalid_qr" = "Kode QR tidak absah."; +"authentication_qr_login_display_subtitle" = "Pindai kode QR di bawah dengan perangkat Anda yang sudah keluar dari akun."; +"authentication_qr_login_confirm_alert" = "Pastikan Anda tahu asalnya dari kode ini. Dengan menautkan perangkat, Anda akan memberikan seseorang akses penuh ke akun Anda."; +"authentication_qr_login_loading_signed_in" = "Anda sekarang masuk di perangkat Anda yang lain."; +"authentication_qr_login_failure_title" = "Penautan gagal"; +"authentication_qr_login_loading_waiting_signin" = "Menunggu perangkat untuk masuk."; +"authentication_qr_login_loading_connecting_device" = "Menghubungkan ke perangkat"; +"authentication_qr_login_confirm_subtitle" = "Konfirmasi bahwa kode di bawah sesuai dengan perangkat Anda yang lain:"; +"authentication_qr_login_confirm_title" = "Koneksi aman dibuat"; +"authentication_qr_login_scan_subtitle" = "Tempatkan kode QR di dalam kotak di bawah"; +"authentication_qr_login_scan_title" = "Pindai kode QR"; +"authentication_qr_login_display_step2" = "Pilih ‘Masuk dengan kode QR’"; +"authentication_qr_login_display_step1" = "Buka Element di perangkat Anda yang lain"; +"authentication_qr_login_display_title" = "Tautkan sebuah perangkat"; +"authentication_qr_login_start_display_qr" = "Tampilkan kode QR di perangkat ini"; +"authentication_qr_login_start_need_alternative" = "Butuh metode yang lain?"; +"authentication_qr_login_start_step4" = "Pilih ‘Tampilkan kode QR di perangkat ini’"; +"authentication_qr_login_start_step3" = "Pilih ‘Tautkan sebuah perangkat’"; +"authentication_qr_login_start_step2" = "Pergi ke Pengaturan → Keamanan & Privasi"; +"authentication_qr_login_start_step1" = "Buka Element di perangkat Anda yang lain"; +"authentication_qr_login_start_subtitle" = "Gunakan kamera pada perangkat ini untuk memindai kode QR yang ditampilkan di perangkat Anda yang lain:"; +"authentication_qr_login_start_title" = "Pindai kode QR"; +"authentication_login_with_qr" = "Masuk dengan kode QR"; +"wysiwyg_composer_format_action_strikethrough" = "Terapkan format garis bawah"; +"wysiwyg_composer_format_action_underline" = "Terapkan format coret"; +"wysiwyg_composer_format_action_italic" = "Terapkan format miring"; + +// Formatting Actions +"wysiwyg_composer_format_action_bold" = "Terapkan format tebal"; +"wysiwyg_composer_start_action_text_formatting" = "Format Teks"; +"wysiwyg_composer_start_action_polls" = "Pemungutan Suara"; +"wysiwyg_composer_start_action_camera" = "Kamera"; +"wysiwyg_composer_start_action_location" = "Lokasi"; +"wysiwyg_composer_start_action_attachments" = "Lampiran"; +"wysiwyg_composer_start_action_stickers" = "Stiker"; + + +// Mark: - WYSIWYG Composer + +// Send Media Actions +"wysiwyg_composer_start_action_media_picker" = "Pustaka Foto"; +"user_session_details_last_activity" = "Aktivitas terakhir"; +"user_session_item_details_last_activity" = "Aktivitas terakhir %@"; +"user_other_session_clear_filter" = "Hapus saringan"; +"user_other_session_no_unverified_sessions" = "Tidak ditemukan sesi yang belum diverifikasi."; +"user_other_session_no_verified_sessions" = "Tidak ditemukan sesi yang terverifikasi."; +"user_other_session_no_inactive_sessions" = "Tidak ditemukan sesi yang tidak aktif."; +"user_other_session_filter_menu_inactive" = "Tidak aktif"; +"user_other_session_filter_menu_unverified" = "Belum diverifikasi"; +"user_other_session_filter_menu_verified" = "Terverifikasi"; +"user_other_session_filter_menu_all" = "Semua sesi"; +"user_other_session_filter" = "Saring"; +"user_other_session_verified_sessions_header_subtitle" = "Untuk keamanan yang terbaik, keluarkan sesi yang Anda tidak kenal atau tidak digunakan lagi."; +"user_other_session_current_session_details" = "Sesi Anda saat ini"; +"user_other_session_verified_additional_info" = "Sesi ini siap untuk perpesanan aman."; +"user_other_session_unverified_additional_info" = "Verifikasi atau keluarkan sesi ini untuk keamanan dan keandalan yang terbaik."; +"user_session_verification_unknown_additional_info" = "Verifikasi sesi Anda saat ini untuk menampilkan status verifikasi sesi ini."; +"user_session_verification_unknown_short" = "Tidak diketahui"; +"user_session_verification_unknown" = "Status verifikasi tidak diketahui"; +"manage_session_name_info_link" = "Pelajari lebih lanjut"; +/* The placeholder will be replaces with manage_session_name_info_link */ +"manage_session_name_info" = "Harap diketahui bahwa nama sesi juga terlihat ke orang-orang yang Anda berkomunikasi. %@"; +"manage_session_name_hint" = "Nama sesi khusus dapat membantu Anda mengenal perangkat Anda dengan lebih mudah."; +"settings_labs_enable_wysiwyg_composer" = "Coba editor teks kaya (mode teks biasa akan datang)"; diff --git a/Riot/Assets/is.lproj/InfoPlist.strings b/Riot/Assets/is.lproj/InfoPlist.strings index 688960701..e6227c08b 100644 --- a/Riot/Assets/is.lproj/InfoPlist.strings +++ b/Riot/Assets/is.lproj/InfoPlist.strings @@ -3,9 +3,9 @@ "NSLocationWhenInUseUsageDescription" = "Þegar þú deilir staðsetningunni þinni með öðru fólki, þarf Element aðgang að henni til að geta birt hana á landakorti."; "NSFaceIDUsageDescription" = "Face ID er notað til að fá aðgang að forritinu þínu."; "NSCalendarsUsageDescription" = "Skoðaðu áætlaða fundi þína í forritinu."; -"NSContactsUsageDescription" = "Element mun birta tengiliðina þína svo þú getir boðið þeim að spjalla."; +"NSContactsUsageDescription" = "Þeim verður deilt með auðkennisþjóninum þínum til að þú getir fundið tengiliðina þína á Matrix."; "NSMicrophoneUsageDescription" = "Element þarf að fá aðgang að hljóðnemanum þínum fyrir símtöl, upptöku á myndskeiðum og upptöku talskilaboða."; -"NSPhotoLibraryUsageDescription" = "Myndasafnið er notað til að senda myndir og myndskeið."; +"NSPhotoLibraryUsageDescription" = "Leyfðu aðgang að myndum til að geta sent myndir og myndskeið úr myndasafninu."; // Permissions usage explanations -"NSCameraUsageDescription" = "Myndavélin er notuð til að taka myndir og myndskeið og fyrir myndsímtöl."; +"NSCameraUsageDescription" = "Myndavélin er notuð fyrir myndsímtöl og til að taka myndir og myndskeið."; "NSLocationAlwaysAndWhenInUseUsageDescription" = "Þegar þú deilir staðsetningu þinni til annarra, þarf Element aðgang til að geta birt hana á landakorti."; diff --git a/Riot/Assets/is.lproj/Vector.strings b/Riot/Assets/is.lproj/Vector.strings index ad9ba1194..aba02e2d6 100644 --- a/Riot/Assets/is.lproj/Vector.strings +++ b/Riot/Assets/is.lproj/Vector.strings @@ -63,7 +63,7 @@ "room_creation_appearance_name" = "Heiti"; "room_creation_privacy" = "Friðhelgi"; "room_creation_make_private" = "Gera einka"; -"room_recents_favourites_section" = "Eftirlæti"; +"room_recents_favourites_section" = "EFTIRLÆTI"; "room_recents_people_section" = "FÓLK"; "room_recents_conversations_section" = "SPJALLRÁSIR"; "room_recents_no_conversation" = "Engar spjallrásir"; @@ -243,8 +243,8 @@ "media_picker_library" = "Safn"; "media_picker_select" = "Veldu"; // Directory -"directory_title" = "Mappa"; -"directory_server_picker_title" = "Veldu möppu"; +"directory_title" = "Yfirlitsskrá"; +"directory_server_picker_title" = "Veldu yfirlitsskrá"; "directory_server_all_rooms" = "Allar spjallrásir á %@ vefþjóninum"; "directory_server_all_native_rooms" = "Allar innbyggðar Matrix-spjallrásir"; // Others @@ -851,7 +851,7 @@ "space_home_show_all_rooms" = "Sýna allar spjallrásir"; "spaces_coming_soon_title" = "Kemur bráðum"; "spaces_no_result_found_title" = "Engar niðurstöður fundust"; -"space_tag" = "bil"; +"space_tag" = "svæði"; "spaces_suggested_room" = "Tillögur"; "spaces_explore_rooms" = "Kanna spjallrásir"; "leave_space_only_action" = "Ekki yfirgefa neinar spjallrásir"; @@ -2091,10 +2091,10 @@ "threads_beta_title" = "Spjallþræðir"; "threads_notice_done" = "Náði því"; "onboarding_celebration_button" = "Hefjumst handa"; -"onboarding_celebration_message" = "Farðu hvenær sem er í stillingarnar til að breyta notandasniðinu þínu."; +"onboarding_celebration_message" = "Farðu hvenær sem er í stillingarnar til að breyta notandasniðinu þínu"; "onboarding_celebration_title" = "Lítur vel út!"; "onboarding_avatar_accessibility_label" = "Auðkennismynd"; -"onboarding_avatar_message" = "Þú getur breytt þessu hvenær sem er."; +"onboarding_avatar_message" = "Tími til að setja andlit á nafnið"; "onboarding_avatar_title" = "Bættu við auðkennismynd"; "onboarding_display_name_hint" = "Þú getur breytt þessu síðar"; "onboarding_display_name_placeholder" = "Birtingarnafn"; @@ -2177,3 +2177,194 @@ "authentication_login_username" = "Notandanafn / tölvupóstfang / símanúmer"; "authentication_login_title" = "Velkomin(n) aftur!"; "authentication_registration_username" = "Notandanafn"; +"threads_beta_information" = "Haltu samræðum skipulögðum með spjallþráðum.\n\nSpjallþræðir hjálpa til við að halda samræðum við efnið og gerir auðveldara að rekja þær. "; +"room_no_privileges_to_create_group_call" = "Þú þarft að vera stjórnandi eða umsjónarmaður til að hefja símtal."; +"room_accessibility_record_voice_message_hint" = "Tvípikkaðu og haltu niðri til að taka upp."; +"room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Enginn auðkennisþjónn er stilltur þannig að þú getur ekki byrjað spjall við tengilið með því að nota tölvupóstfang."; +"find_your_contacts_title" = "Byrjum á því að gera lista yfir tengiliðina þína"; +"contacts_address_book_permission_denied_alert_message" = "Til að virkja tengiliði, skaltu fara í stillingar tækisins þíns."; +"rooms_empty_view_information" = "Spjallrásir eru frábærar fyrir hópspjall, einka eða opinbert. Ýttu á + til að finna fyrirliggjandi spjallrásir eða búa til nýjar."; +"people_empty_view_information" = "Spjallaðu á öruggan hátt við hvern sem er. Ýttu á + til að bæta við fólki."; +"room_creation_error_invite_user_by_email_without_identity_server" = "Enginn auðkennisþjónn er stilltur þannig að þú getur ekki byrjað spjall við tengilið með því að nota tölvupóstfang."; + +// Errors +"error_user_already_logged_in" = "Það lítur út fyrir að þú sért að reyna að tengjast öðrum heimaþjóni. Viltu skrá þig út?"; +"create_room_show_in_directory_footer" = "Þetta hjálpar fólki að finna og taka þátt."; +"room_access_settings_screen_upgrade_alert_message" = "Hver sem er í %@ mun geta fundið og tekið þátt í þessari spjallrás - ekki er þörf á að bjóða öllum handvirkt. Þú munt geta breytt þessu í stillingum spjallrásarinnar hvenær sem er."; +"room_access_settings_screen_restricted_message" = "Hver sem er í svæði getur fundið og tekið þátt. \nÞý verður beðin/n um að staðfesta hvaða svæði."; +"room_access_settings_screen_private_message" = "Aðeins fólk sem er boðið getur fundið og tekið þátt."; +"room_access_settings_screen_message" = "Veldu hverjir geta fundið %@ og tekið þátt."; +"auth_reset_password_error_is_required" = "Enginn auðkennisþjónn er stilltur: bættu við einum slíkum í stillingum fyrir netþjónninn til að geta endurstillt Matrix-lykilorðið þitt."; +"auth_forgot_password_error_no_configured_identity_server" = "Enginn auðkennisþjónn er stilltur: bættu við einum slíkum til að geta endurstillt Matrix-lykilorðið þitt."; +"auth_phone_is_required" = "Enginn auðkennisþjónn er stilltur, þannig að þú getur ekki bætt við símanúmeri til að geta í framtíðinni endurstillt Matrix-lykilorðið þitt."; +"auth_email_is_required" = "Enginn auðkennisþjónn er stilltur, þannig að þú getur ekki bætt við tölvupóstfangi til að geta í framtíðinni endurstillt Matrix-lykilorðið þitt."; +"auth_add_email_phone_message_2" = "Notaðu tölvupóstfang til að endurheimta aðganginn þinn. Notaðu síðar tölvupóstfang eða símanúmer til að vera geta verið finnanleg/ur fyrir fólk sem þekkir þig."; +"auth_add_phone_message_2" = "Notaðu símanúmer til að vera geta verið finnanleg/ur fyrir fólk sem þekkir þig."; +"auth_add_email_message_2" = "Notaðu tölvupóstfang til að endurheimta aðganginn þinn, og síðar til að vera geta verið finnanleg/ur fyrir fólk sem þekkir þig."; +"authentication_terms_policy_url_error" = "Tókst ekki að finna viðkomandi stefnu. Reyndu aftur síðar."; +"authentication_cancel_flow_confirmation_message" = "Ekki er enn búið að útbúa notandaaðganginn þinn. Á að hætta skráningarferlinu?"; +"authentication_server_selection_generic_error" = "Finn ekki heimaþjón á þessari slóð, athugaðu hvort slóðin sé rétt."; +"authentication_server_selection_register_message" = "Hvert er vistfang netþjónsins þíns? Þetta er staður sem geymir öll gögnin þín"; +"onboarding_display_name_message" = "Þetta verður birt þegar þú sendir skilaboð."; +"onboarding_congratulations_personalize_button" = "Persónugerðu forsíðuna"; +"notice_room_join_rule_public_by_you_for_dm" = "Þú gerðir þetta opinbert."; +"notice_room_join_rule_public_by_you" = "Þú gerðir spjallrásina opinbera."; +"notice_room_join_rule_public_for_dm" = "%@ gerði þetta opinbert."; +"notice_room_join_rule_public" = "%@ gerði spjallrásina opinbera."; +"notice_room_history_visible_to_members_from_joined_point_for_dm" = "%@ gerði skilaboð héðan í frá sýnileg fyrir alla meðlimi spjallrásarinnar síðan þeir skráðu sig."; +"notice_room_history_visible_to_members_from_joined_point" = "%@ gerði ferilskrá spjallrásar héðan í frá sýnilega fyrir alla meðlimi spjallrásarinnar síðan þeir skráðu sig."; +"notice_room_history_visible_to_members_from_invited_point_for_dm" = "%@ gerði skilaboð héðan í frá sýnileg fyrir alla meðlimi spjallrásarinnar síðan þeim var boðið."; +"notice_room_history_visible_to_members_from_invited_point" = "%@ gerði ferilskrá spjallrásar héðan í frá sýnilega fyrir alla meðlimi spjallrásarinnar síðan þeim var boðið."; +"notice_room_history_visible_to_members_for_dm" = "%@ gerði skilaboð héðan í frá sýnileg fyrir alla meðlimi spjallrásarinnar."; +"notice_room_join_rule_invite_for_dm" = "%@ gerði þetta einungis aðgengilegt gegn boði."; +// New +"notice_room_join_rule_invite" = "%@ gerði spjallrásina einungis aðgengilega gegn boði."; +// Old +"notice_room_join_rule" = "Reglan fyrir þátttöku er: %@"; +"location_sharing_live_lab_promotion_title" = "Deiling staðsetningar í rauntíma"; +"location_sharing_live_stop_sharing_progress" = "Stöðva deilingu staðsetninga"; +"location_sharing_live_stop_sharing_error" = "Mistókst að stöðva deilingu staðsetninga"; +"location_sharing_live_no_user_locations_error_title" = "Engar staðsetningar notenda tiltækar"; +"location_sharing_live_error" = "Villa í rauntímastaðsetningu"; +"live_location_sharing_ended" = "Staðsetningu í rauntíma lauk"; +"location_sharing_map_loading_error" = "Tókst ekki að hlaða inn landakorti\nHeimaþjónninn er ekki stilltur til að birta landakort"; +"location_sharing_invalid_power_level_title" = "Þú hefur ekki heimildir til að deila rauntímastaðsetningum"; +"room_invites_empty_view_information" = "Þetta er þar sem boðsgestirnir þínir birtast."; + +// Mark: - Room invites + +"room_invites_empty_view_title" = "Ekkert nýtt."; +"all_chats_onboarding_page_title1" = "Velkomin í nýja sýn!"; +"all_chats_nothing_found_placeholder_message" = "Reyndu að aðlaga leitina þína."; +"all_chats_edit_layout_alphabetical_order" = "Raða A-Ö"; +"all_chats_edit_layout_activity_order" = "Raða eftir virkni"; +"all_chats_edit_layout_sorting_options_title" = "Raða skilaboðum eftir"; +"all_chats_edit_layout_add_filters_title" = "Síaðu skilaboðin þín"; +"all_chats_edit_layout_add_section_title" = "Bæta við hlutanum á forsíðu"; +"all_chats_edit_layout" = "Kjörstillingar framsetningar"; +"all_chats_section_title" = "Spjallrásir"; + +// Mark: - All Chats + +"all_chats_title" = "Allar spjallrásir"; +"room_intro_cell_information_room_without_topic_sentence2_part2" = " svo fólk viti að um hvað málin snúist."; +"share_invite_link_space_text" = "Hæ, taktu þátt í þessu svæði á %@"; +"share_invite_link_room_text" = "Hæ, taktu þátt í þessari spjallrás á %@"; +"create_room_suggest_room" = "Stinga uppá við meðlimi svæðis"; +"room_details_promote_room_title" = "Hækka spjallrás"; +"room_first_message_placeholder" = "Sendu fyrstu skilaboðin þín…"; +"room_participants_security_information_room_encrypted_for_dm" = "Skilaboð hér eru enda-í-enda dulrituð.\n\nÖryggi skilaboðanna þinna er tryggt og einungis þú og viðtakendurnir hafa dulritunarlyklana til að opna skilaboðin."; +"room_participants_security_information_room_encrypted" = "Skilaboð á þessari spjallrás eru enda-í-enda dulrituð.\n\nÖryggi skilaboðanna þinna er tryggt og einungis þú og viðtakendurnir hafa dulritunarlyklana til að opna skilaboðin."; +"room_participants_invite_prompt_to_msg" = "Ertu viss um að þú viljir bjóða %@ á %@?"; +"password_validation_error_contain_symbol" = "Innihalda tákn."; +"password_validation_error_contain_number" = "Innihalda tölu."; +"password_validation_error_contain_uppercase_letter" = "Innihalda hástaf."; +"password_validation_error_contain_lowercase_letter" = "Innihalda lágstaf."; +/* The placeholder will show a number */ +"password_validation_error_max_length" = "Ekki vera lengra en %d stafir."; +/* The placeholder will show a number */ +"password_validation_error_min_length" = "Að minnsta kosti %d stafa langt."; +"password_validation_error_header" = "Uppgefið lykilorð uppfyllir ekki eftirfarandi skilyrði:"; + +// MARK: Password Validation +"password_validation_info_header" = "Lykilorðið þitt ætti að uppfylla eftirfarandi skilyrði:"; +/* The placeholder will show the homeserver's domain */ +"authentication_terms_message" = "Endilega lestu í gegnum stefnur og skilmála fyrir %@"; +"authentication_terms_title" = "persónuverndarstefna"; +/* The placeholder will show the phone number that was entered. */ +"authentication_verify_msisdn_waiting_message" = "Kóði var sendur til: %@"; +"authentication_verify_msisdn_waiting_title" = "Sannreyndu símanúmerið þitt"; +"authentication_verify_msisdn_otp_text_field_placeholder" = "Staðfestingarkóði"; +/* The placeholder will show the homeserver's domain */ +"authentication_verify_msisdn_input_message" = "%@ þarf að sannreyna notandaaðganginn þinn"; +"authentication_choose_password_not_verified_title" = "Tölvupóstfang ekki staðfest"; +"authentication_choose_password_signout_all_devices" = "Skrá út af öllum tækjum"; +"authentication_choose_password_input_message" = "Hafðu það að minnsta kosti 8 stafa langt"; +/* The placeholder will show the email address that was entered. */ +"authentication_forgot_password_waiting_message" = "Farðu eftir leiðbeiningunum sem sendar voru á %@"; +/* The placeholder will show the homeserver's domain */ +"authentication_forgot_password_input_message" = "%@ mun senda þér staðfestingartengil"; +"authentication_verify_email_waiting_hint" = "Fékkstu ekki tölvupóst?"; +/* The placeholder will show the email address that was entered. */ +"authentication_verify_email_waiting_message" = "Farðu eftir leiðbeiningunum sem sendar voru á %@"; +/* The placeholder will show the homeserver's domain */ +"authentication_verify_email_input_message" = "%@ þarf að sannreyna notandaaðganginn þinn"; +"authentication_server_selection_register_title" = "Veldu heimaþjóninn þinn"; +"authentication_server_selection_login_message" = "Hvert er vistfang netþjónsins þíns?"; +"authentication_server_selection_login_title" = "Tengjast við heimaþjón"; +"authentication_server_info_title_login" = "Þar sem samtölin þín eru"; +"authentication_server_info_title" = "Þar sem samtölin þín verða"; +"authentication_registration_password_footer" = "Verður að vera að minnsta kosti 8 stafir"; +/* The placeholder will show the full Matrix ID that has been entered. */ +"authentication_registration_username_footer_available" = "Aðrir geta fundið þig %@"; +"authentication_registration_username_footer" = "Þú getur ekki breytt þessu síðar"; +"onboarding_display_name_max_length" = "Birtingarnafnið þitt verður að vera styttra en 256 stafir"; +"onboarding_congratulations_home_button" = "Fara á forsíðuna"; +/* The placeholder string contains the user's matrix ID */ +"onboarding_congratulations_message" = "%@ aðgangur þinn hefur verið útbúinn"; +"onboarding_use_case_existing_server_message" = "Ætlarðu að ganga til liðs við fyrirliggjandi netþjón?"; +"onboarding_use_case_title" = "Við hverja muntu helst spjalla?"; +"onboarding_splash_page_4_message" = "Element er líka frábært fyrir vinnustaðinn. Heimsins öruggustu samtök treysta því."; +"onboarding_splash_page_4_title_no_pun" = "Skilaboð fyrir teymið þitt."; +"onboarding_splash_page_3_message" = "Enda-í-enda dulritað og ekkert símanúmer nauðsynlegt. Engar auglýsingar eða gagnasöfnun."; +"onboarding_splash_page_2_message" = "Veldu hvar á að geyma samtölin þín, sem gefur þér stjórnina og algert sjálfstæði. Tengt í gegnum Matrix."; +"onboarding_splash_page_1_message" = "Örugg og óháð samskipti sem gefa þér færi á að ræða málin í friði rétt eins og þetta sé maður á mann í heimahúsi."; +"invite_to" = "Bjóða í %@"; +"call_consulting_with_user" = "Ráðfæri við %@"; +"message_reply_to_sender_sent_their_live_location" = "Staðsetning í rauntíma."; +"notice_room_history_visible_to_members" = "%@ gerði ferilskrá spjallrásar héðan í frá sýnilega fyrir alla meðlimi spjallrásarinnar."; +"notice_room_history_visible_to_anyone" = "%@ gerði ferilskrá spjallrásar héðan í frá sýnilega fyrir hvern sem er."; +"device_name_mobile" = "%@ fyrir farsíma"; +"device_name_web" = "%@ á vefnum"; +"device_name_desktop" = "%@ fyrir einkatölvur"; +"user_session_item_details" = "%@ · Síðasta virkni %@"; +"location_sharing_live_loading" = "Hleð inn rauntímastaðsetningu..."; +"location_sharing_live_list_item_time_left" = "%@ fór"; +"location_sharing_map_credits_title" = "© Höfundarréttur"; +"location_sharing_post_failure_title" = "Við gátum ekki sent staðsetninguna þína"; +"space_invite_nav_title" = "Boð á svæði"; +"space_detail_nav_title" = "Nánar um svæði"; +"space_selector_empty_view_information" = "Svæði eru ný leið til að hópa fólk og spjallrásir. Útbúðu svæði til að komast í gang."; +"space_selector_empty_view_title" = "Engin svæði ennþá."; + +// Mark: - Space Selector + +"space_selector_title" = "Svæðin mín"; +"all_chats_onboarding_title" = "Hvað er nýtt"; +"all_chats_onboarding_page_title3" = "Gefðu umsögn"; +"all_chats_onboarding_page_title2" = "Aðgangur að svæðum"; +"all_chats_user_menu_settings" = "Notandastillingar"; +"all_chats_edit_layout_pin_spaces_title" = "Festu svæðin þín"; + +// MARK: Reactions + +"room_event_action_reaction_more" = "%@ til viðbótar"; +"leave_space_selection_no_rooms" = "Velja engar spjallrásir"; +"leave_space_selection_all_rooms" = "Velja allar spjallrásir"; +"leave_space_selection_title" = "VELJA SPJALLRÁSIR"; +"leave_space_and_more_rooms" = "Yfirgefa svæði og %@ spjallrásir"; +"leave_space_and_one_room" = "Yfirgefa svæði og 1 spjallrás"; +"spaces_creation_invite_by_username_message" = "Þú getur boðið þeim síðar."; +"spaces_creation_add_rooms_title" = "Hverju viltu bæta við?"; +"spaces_creation_new_rooms_message" = "Við búum til spjallrás fyrir hvern og einn þeirra."; +"spaces_invites_coming_soon_title" = "Boð á spjallrásir koma bráðum"; +"spaces_add_rooms_coming_soon_title" = "Að bæta við spjallrásum kemur bráðum"; +"spaces_create_subspace_title" = "Búa til undirsvæði"; +"spaces_add_subspace_title" = "Búa til svæði innan %@"; +"space_invite_not_enough_permission" = "Þú hefur ekki heimild til að bjóða fólk á þetta svæði"; +"room_invite_not_enough_permission" = "Þú hefur ekki heimild til að bjóða fólk í þessa spjallrás"; +"home_context_menu_mark_as_read" = "Merkja sem lesið"; +"create_room_promotion_header" = "KYNNING"; +"pin_protection_reset_alert_message" = "Til að endurstilla PIN-númerið, þarftu að skrá þig inn aftur og útbúa nýtt"; +"major_update_information" = "Við iðum í skinninu eftir að tilkynna að við höfum skipt um nafn! Forritið er að fullu uppfært og þú ert skráð/ur aftur inn á aðganginn þinn."; +"widget_sticker_picker_no_stickerpacks_alert" = "Í augnablikinu ertu ekki með neina límmerkjapakka virkjaða."; +"room_access_space_chooser_known_spaces_section" = "Svæði sem þú þekkir sem innihalda %@"; +"room_details_promote_room_suggest_title" = "Stinga uppá við meðlimi svæðis"; + +// User sessions management +"user_sessions_settings" = "Sýsla með setur"; +"settings_labs_enable_auto_report_decryption_errors" = "Tilkynna afkóðunarvillur sjálfvirkt"; +"settings_timeline" = "TÍMALÍNA"; + +// MARK: Authentication +"authentication_registration_title" = "Búðu til aðganginn þinn"; diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index ec31cebb3..671e2d08b 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -2469,7 +2469,7 @@ "device_name_mobile" = "%@ Mobile"; "device_name_web" = "%@ Web"; "device_name_desktop" = "%@ Desktop"; -"user_session_item_details" = "%@ · Ultima attività %@"; +"user_session_item_details" = "%1$@ · %2$@"; // First item is client name and second item is session display name "user_session_name" = "%@: %@"; @@ -2482,8 +2482,127 @@ "user_session_verified_short" = "Verificata"; "user_session_unverified" = "Sessione non verificata"; "user_session_verified" = "Sessione verificata"; -"user_sessions_overview_current_session_section_title" = "SESSIONE ATTUALE"; +"user_sessions_overview_current_session_section_title" = "Sessione attuale"; "user_sessions_overview_other_sessions_section_info" = "Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più."; -"user_sessions_overview_other_sessions_section_title" = "ALTRE SESSIONI"; +"user_sessions_overview_other_sessions_section_title" = "Altre sessioni"; "settings_labs_enable_new_app_layout" = "Nuova disposizione dell'applicazione"; "room_first_message_placeholder" = "Invia il tuo primo messaggio…"; +"room_event_encryption_info_key_authenticity_not_guaranteed" = "L'autenticità di questo messaggio cifrato non può essere garantita su questo dispositivo."; +"user_session_overview_session_details_button_title" = "Dettagli sessione"; +"user_session_overview_session_title" = "Sessione"; +"user_session_overview_current_session_title" = "Sessione attuale"; +"user_session_details_application_url" = "URL"; +"user_session_details_application_version" = "Versione"; +"user_session_details_application_name" = "Nome"; +"user_session_details_device_os" = "Sistema operativo"; +"user_session_details_device_browser" = "Browser"; +"user_session_details_device_model" = "Modello"; +"user_session_details_device_ip_location" = "Posizione IP"; +"user_session_details_device_ip_address" = "Indirizzo IP"; +"user_session_details_session_section_footer" = "Copia qualsiasi dato tenendolo premuto."; +"user_session_details_session_id" = "ID sessione"; +"user_session_details_session_name" = "Nome sessione"; +"user_session_details_device_section_header" = "Dispositivo"; +"user_session_details_application_section_header" = "Applicazione"; +"user_session_details_session_section_header" = "Sessione"; +"user_session_details_title" = "Dettagli sessione"; +"user_session_push_notifications_message" = "Quando attivo, questa sessione riceverà notifiche push."; +"user_session_push_notifications" = "Notifiche push"; +"user_sessions_view_all_action" = "Vedi tutte (%d)"; +"user_sessions_overview_security_recommendations_inactive_info" = "Considera di disconnettere le sessioni vecchie (90 giorni o più) che non usi più."; +"user_sessions_overview_security_recommendations_inactive_title" = "Sessioni inattive"; +"user_sessions_overview_security_recommendations_unverified_info" = "Verifica o disconnetti le sessioni non verificate."; +"user_sessions_overview_security_recommendations_unverified_title" = "Sessioni non verificate"; +"user_sessions_overview_security_recommendations_section_info" = "Migliora la sicurezza del tuo account seguendo questi consigli."; +"user_sessions_overview_security_recommendations_section_title" = "Consigli di sicurezza"; +"all_chats_user_menu_accessibility_label" = "Menu utente"; +"settings_labs_enable_new_client_info_feature" = "Registra il nome, la versione e l'url del client per riconoscere le sessioni più facilmente nel gestore di sessioni"; +"settings_labs_enable_new_session_manager" = "Nuovo gestore di sessioni"; +"device_type_name_unknown" = "Sconosciuto"; +"device_type_name_mobile" = "Mobile"; +"device_type_name_web" = "Web"; +"device_type_name_desktop" = "Desktop"; +"user_inactive_session_item" = "Inattiva da 90+ giorni"; +"user_inactive_session_item_with_date" = "Inattiva da 90+ giorni (%@)"; +"user_other_session_unverified_sessions_header_subtitle" = "Verifica le tue sessioni per avere conversazioni più sicure o disconnetti quelle che non riconosci o che non usi più."; +"user_other_session_security_recommendation_title" = "Consiglio di sicurezza"; +"user_sessions_overview_link_device" = "Collega un dispositivo"; + +// MARK: User sessions management + +// Parameter is the application display name (e.g. "Element") +"user_sessions_default_session_display_name" = "%@ iOS"; +"sign_out_confirmation_message" = "Vuoi davvero disconnetterti?"; + +// MARK: Sign out warning + +"sign_out" = "Disconnetti"; +"manage_session_rename" = "Rinomina sessione"; +"authentication_qr_login_failure_retry" = "Riprova"; +"authentication_qr_login_failure_request_timed_out" = "Il collegamento non è stato completato nel tempo previsto."; +"authentication_qr_login_failure_request_denied" = "La richiesta è stata negata sull'altro dispositivo."; +"authentication_qr_login_failure_invalid_qr" = "Codice QR non valido."; +"authentication_qr_login_failure_title" = "Collegamento fallito"; +"authentication_qr_login_loading_signed_in" = "Ora hai fatto l'accesso sull'altro dispositivo."; +"authentication_qr_login_loading_waiting_signin" = "In attesa che il dispositivo acceda."; +"authentication_qr_login_loading_connecting_device" = "Connessione al dispositivo"; +"authentication_qr_login_confirm_alert" = "Assicurati di conoscere l'origine di questo codice. Collegando i dispositivi, fornirai a qualcuno l'accesso totale al tuo account."; +"authentication_qr_login_confirm_subtitle" = "Conferma che il codice sottostante corrisponda nell'altro dispositivo:"; +"authentication_qr_login_confirm_title" = "Connessione sicura stabilita"; +"authentication_qr_login_scan_subtitle" = "Posiziona il codice QR nel riquadro sotto"; +"authentication_qr_login_scan_title" = "Scansiona codice QR"; +"authentication_qr_login_display_step2" = "Seleziona ‘Accedi con codice QR’"; +"authentication_qr_login_display_step1" = "Apri Element sull'altro dispositivo"; +"authentication_qr_login_display_subtitle" = "Scansiona il codice QR sottostante con il dispositivo che è disconnesso."; +"authentication_qr_login_display_title" = "Collega un dispositivo"; +"authentication_qr_login_start_display_qr" = "Mostra codice QR in questo dispositivo"; +"authentication_qr_login_start_need_alternative" = "Serve un metodo alternativo?"; +"authentication_qr_login_start_step4" = "Seleziona ‘Mostra codice QR in questo dispositivo’"; +"authentication_qr_login_start_step3" = "Seleziona ‘Collega un dispositivo’"; +"authentication_qr_login_start_step2" = "Vai in Impostazioni -> Sicurezza & Privacy"; +"authentication_qr_login_start_step1" = "Apri Element sull'altro dispositivo"; +"authentication_qr_login_start_subtitle" = "Usa la fotocamera di questo dispositivo per scansionare il codice QR mostrato nell'altro dispositivo:"; +"authentication_qr_login_start_title" = "Scansiona codice QR"; +"authentication_login_with_qr" = "Accedi con codice QR"; +"wysiwyg_composer_format_action_strikethrough" = "Applica formato sottolineato"; +"wysiwyg_composer_format_action_underline" = "Applica formato sbarrato"; +"wysiwyg_composer_format_action_italic" = "Applica formato corsivo"; + +// Formatting Actions +"wysiwyg_composer_format_action_bold" = "Applica formato grassetto"; +"wysiwyg_composer_start_action_text_formatting" = "Formattazione testo"; +"wysiwyg_composer_start_action_camera" = "Fotocamera"; +"wysiwyg_composer_start_action_location" = "Posizione"; +"wysiwyg_composer_start_action_polls" = "Sondaggi"; +"wysiwyg_composer_start_action_attachments" = "Allegati"; +"wysiwyg_composer_start_action_stickers" = "Adesivi"; + + +// Mark: - WYSIWYG Composer + +// Send Media Actions +"wysiwyg_composer_start_action_media_picker" = "Album di foto"; +"user_session_details_last_activity" = "Ultima attività"; +"user_session_item_details_last_activity" = "Ultima attività %@"; +"user_other_session_clear_filter" = "Annulla filtro"; +"user_other_session_no_unverified_sessions" = "Nessuna sessione non verificata trovata."; +"user_other_session_no_verified_sessions" = "Nessuna sessione verificata trovata."; +"user_other_session_no_inactive_sessions" = "Nessuna sessione inattiva trovata."; +"user_other_session_filter_menu_inactive" = "Inattive"; +"user_other_session_filter_menu_unverified" = "Non verificate"; +"user_other_session_filter_menu_verified" = "Verificate"; +"user_other_session_filter_menu_all" = "Tutte le sessioni"; +"user_other_session_filter" = "Filtra"; +"user_other_session_verified_sessions_header_subtitle" = "Per una maggiore sicurezza, disconnetti tutte le sessioni che non riconosci o che non usi più."; +"user_other_session_current_session_details" = "La sessione attuale"; +"user_other_session_verified_additional_info" = "Questa sessione è pronta per i messaggi sicuri."; +"user_other_session_unverified_additional_info" = "Verifica o disconnetti questa sessione per una migliore sicurezza e affidabilità."; +"user_session_verification_unknown_additional_info" = "Verifica l'attuale sessione per rivelare lo stato di verifica di questa sessione."; +"user_session_verification_unknown_short" = "Sconosciuto"; +"user_session_verification_unknown" = "Stato di verifica sconosciuto"; +"manage_session_name_info_link" = "Maggiori info"; +/* The placeholder will be replaces with manage_session_name_info_link */ +"manage_session_name_info" = "Ricorda che i nomi di sessione sono anche visibili alle persone con cui comunichi. %@"; +"manage_session_name_hint" = "I nomi di sessione personalizzati possono aiutarti a riconoscere i tuoi dispositivi più facilmente."; +"settings_labs_enable_wysiwyg_composer" = "Prova l'editor in rich text (il testo semplice è in arrivo)"; +"settings_labs_enable_voice_broadcast" = "Broadcast voce (in sviluppo attivo). Attualmente rileviamo solo il broadcast vocale nella linea temporale della stanza, non è possibile inviare o ascoltare un vero broadcast vocale"; diff --git a/Riot/Assets/pt_BR.lproj/Vector.strings b/Riot/Assets/pt_BR.lproj/Vector.strings index 362dd9620..3d6b5ee57 100644 --- a/Riot/Assets/pt_BR.lproj/Vector.strings +++ b/Riot/Assets/pt_BR.lproj/Vector.strings @@ -2470,7 +2470,7 @@ "device_name_mobile" = "%@ Mobile"; "device_name_web" = "%@ Web"; "device_name_desktop" = "%@ Desktop"; -"user_session_item_details" = "%@ · Última atividade %@"; +"user_session_item_details" = "%1$@ · %2$@"; // First item is client name and second item is session display name "user_session_name" = "%@: %@"; @@ -2483,8 +2483,126 @@ "user_session_verified_short" = "Verificada"; "user_session_unverified" = "Sessão não-verificada"; "user_session_verified" = "Sessão verificada"; -"user_sessions_overview_current_session_section_title" = "SESSÃO ATUAL"; +"user_sessions_overview_current_session_section_title" = "Sessão atual"; "user_sessions_overview_other_sessions_section_info" = "Para melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais."; -"user_sessions_overview_other_sessions_section_title" = "OUTRAS SESSÕES"; +"user_sessions_overview_other_sessions_section_title" = "Outras sessões"; "settings_labs_enable_new_app_layout" = "Novo Layout de Aplicativo"; "room_first_message_placeholder" = "Envie sua primeira mensagem…"; +"user_session_push_notifications_message" = "Quando ativada, esta sessão vai receber notificações push."; +"room_event_encryption_info_key_authenticity_not_guaranteed" = "A autenticidade desta mensagem encriptada não pode ser garantida neste dispositivo."; +"user_session_overview_session_details_button_title" = "Detalhes da sessão"; +"user_session_overview_session_title" = "Sessão"; +"user_session_overview_current_session_title" = "Sessão atual"; +"user_session_details_application_url" = "URL"; +"user_session_details_application_version" = "Versão"; +"user_session_details_application_name" = "Nome"; +"user_session_details_device_os" = "Sistema Operativo"; +"user_session_details_device_browser" = "Browser"; +"user_session_details_device_model" = "Modelo"; +"user_session_details_device_ip_location" = "Localização de IP"; +"user_session_details_device_ip_address" = "Endereço de IP"; +"user_session_details_session_section_footer" = "Copie qualquer dado ao tocar nele e segurá-lo."; +"user_session_details_session_id" = "ID da sessão"; +"user_session_details_title" = "Detalhes da sessão"; +"user_session_details_session_name" = "Nome da sessão"; +"user_session_details_device_section_header" = "Dispositivo"; +"user_session_details_application_section_header" = "Aplicativo"; +"user_session_details_session_section_header" = "Sessão"; +"user_session_push_notifications" = "Notificações push"; +"user_sessions_view_all_action" = "Ver todas (%d)"; +"user_sessions_overview_security_recommendations_inactive_info" = "Considere fazer signout de sessões antigas (90 dias ou mais antigo) que você não usa mais."; +"user_sessions_overview_security_recommendations_inactive_title" = "Sessões inativas"; +"user_sessions_overview_security_recommendations_unverified_info" = "Verifique ou faça signout de sessões não-verificadas."; +"user_sessions_overview_security_recommendations_unverified_title" = "Sessões não-verificadas"; +"user_sessions_overview_security_recommendations_section_info" = "Melhore a segurança de sua conta ao seguir esta recomendações."; +"user_sessions_overview_security_recommendations_section_title" = "Recomendações de segurança"; +"all_chats_user_menu_accessibility_label" = "Menu de usuária(o)"; +"settings_labs_enable_new_client_info_feature" = "Gravar o nome de cliente, versão, e url para reconhecer sessões mais facilmente em gerenciador de sessão"; +"settings_labs_enable_new_session_manager" = "Novo gerenciador de sessão"; +"device_type_name_unknown" = "Desconhecido"; +"device_type_name_mobile" = "Mobile"; +"device_type_name_web" = "Web"; +"device_type_name_desktop" = "Desktop"; +"user_inactive_session_item_with_date" = "Inativa por 90+ dias (%@)"; +"user_inactive_session_item" = "Inativa por 90+ dias"; +"user_other_session_unverified_sessions_header_subtitle" = "Verifique suas sessões para mensageria de segurança melhorada ou faça signout daquelas que você não reconhece ou usa mais."; +"user_other_session_security_recommendation_title" = "Recomendação de segurança"; +"user_sessions_overview_link_device" = "Linkar um dispositivo"; + +// MARK: User sessions management + +// Parameter is the application display name (e.g. "Element") +"user_sessions_default_session_display_name" = "%@ iOS"; +"sign_out_confirmation_message" = "Tem certeza que você quer fazer signout?"; + +// MARK: Sign out warning + +"sign_out" = "Fazer signout"; +"manage_session_rename" = "Renomear sessão"; +"authentication_qr_login_failure_retry" = "Tentar de novo"; +"authentication_qr_login_failure_request_timed_out" = "A linkagem não foi completada no tempo requerido."; +"authentication_qr_login_failure_request_denied" = "A requisição foi negada no outro dispositivo."; +"authentication_qr_login_failure_invalid_qr" = "QR code é inválido."; +"authentication_qr_login_failure_title" = "Linkagem falhou"; +"authentication_qr_login_loading_signed_in" = "Você está agora feito signin em seu outro dispositivo."; +"authentication_qr_login_loading_waiting_signin" = "Esperando por dispositivo para fazer signin."; +"authentication_qr_login_loading_connecting_device" = "Conectando a dispositivo"; +"authentication_qr_login_confirm_alert" = "Por favor assegure que você sabe a origem deste código. Ao linkar dispositivos, você vai prover alguém com acesso completo a sua conta."; +"authentication_qr_login_confirm_subtitle" = "Confirme que o código abaixo correspondem com seu outro dispositivo:"; +"authentication_qr_login_confirm_title" = "Conexão segura estabelecida"; +"authentication_qr_login_scan_subtitle" = "Posicione o QR code no quadrado abaixo"; +"authentication_qr_login_scan_title" = "Scannar QR code"; +"authentication_qr_login_display_step2" = "Selecione ‘Fazer signin com QR code’"; +"authentication_qr_login_display_step1" = "Abra Element em seu outro dispositivo"; +"authentication_qr_login_display_subtitle" = "Scanne o QR code abaixo com seu dispositivo que está feito signout."; +"authentication_qr_login_display_title" = "Linkar um dispositivo"; +"authentication_qr_login_start_display_qr" = "Mostrar QR code neste dispositivo"; +"authentication_qr_login_start_need_alternative" = "Precisa de um método alternativo?"; +"authentication_qr_login_start_step4" = "Selecione ‘Mostrar QR code neste dispositivo’"; +"authentication_qr_login_start_step3" = "Selecione ‘Linkar um dispositivo’"; +"authentication_qr_login_start_step2" = "Vá para Ajustes -> Segurança & Privacidade"; +"authentication_qr_login_start_step1" = "Abra Element em seu outro dispositivo"; +"authentication_qr_login_start_subtitle" = "Use a câmera neste dispositivo para scannar o QR code mostrado em seu outro dispositivo:"; +"authentication_qr_login_start_title" = "Scannar QR code"; +"authentication_login_with_qr" = "Fazer signin com QR code"; +"wysiwyg_composer_format_action_strikethrough" = "Aplicar formato sublinhar"; +"wysiwyg_composer_format_action_underline" = "Aplicar formato tachar"; +"wysiwyg_composer_format_action_italic" = "Aplicar formato itálico"; + +// Formatting Actions +"wysiwyg_composer_format_action_bold" = "Aplicar formato negrito"; +"wysiwyg_composer_start_action_text_formatting" = "Formatação de Texto"; +"wysiwyg_composer_start_action_camera" = "Câmera"; +"wysiwyg_composer_start_action_location" = "Localização"; +"wysiwyg_composer_start_action_polls" = "Sondagens"; +"wysiwyg_composer_start_action_attachments" = "Anexos"; +"wysiwyg_composer_start_action_stickers" = "Stickers"; + + +// Mark: - WYSIWYG Composer + +// Send Media Actions +"wysiwyg_composer_start_action_media_picker" = "Biblioteca de Fotos"; +"user_session_details_last_activity" = "Última atividade"; +"user_session_item_details_last_activity" = "Última atividade %@"; +"user_other_session_clear_filter" = "Limpar filtro"; +"user_other_session_no_unverified_sessions" = "Nenhuma sessão não-verificada encontrada."; +"user_other_session_no_verified_sessions" = "Nenhuma sessão verificada encontrada."; +"user_other_session_no_inactive_sessions" = "Nenhuma sessão inativa encontrada."; +"user_other_session_filter_menu_inactive" = "Inativas"; +"user_other_session_filter_menu_unverified" = "Não-verificadas"; +"user_other_session_filter_menu_verified" = "Verificadas"; +"user_other_session_filter_menu_all" = "Todas as sessões"; +"user_other_session_filter" = "Filtrar"; +"user_other_session_verified_sessions_header_subtitle" = "Para melhor segurança, faça signout de qualquer sessão que você não reconhece ou usa mais."; +"user_other_session_current_session_details" = "Sua sessão atual"; +"user_other_session_verified_additional_info" = "Esta sessão está pronta para mensageria segura."; +"user_other_session_unverified_additional_info" = "Verifique ou faça signout desta sessão para melhor segurança e fiabilidade."; +"user_session_verification_unknown_additional_info" = "Verifique sua sessão atual para revelar o status de verificação desta sessão."; +"user_session_verification_unknown_short" = "Desconhecido"; +"user_session_verification_unknown" = "Status de verificação desconhecido"; +"manage_session_name_info_link" = "Saber mais"; +/* The placeholder will be replaces with manage_session_name_info_link */ +"manage_session_name_info" = "Por favor esteja ciente que nomes de sessões também são visíveis a pessoas com quem você se comunica. %@"; +"manage_session_name_hint" = "Nomes de sessões personalizados podem ajudar você a reconhecer seus dispositivos mais facilmente."; +"settings_labs_enable_wysiwyg_composer" = "Experimente o editor de texto rico (modo de texto puro vindo em breve)"; diff --git a/Riot/Assets/ru.lproj/Vector.strings b/Riot/Assets/ru.lproj/Vector.strings index fd018bde0..5c3fb89eb 100644 --- a/Riot/Assets/ru.lproj/Vector.strings +++ b/Riot/Assets/ru.lproj/Vector.strings @@ -2153,3 +2153,8 @@ "authentication_server_selection_login_title" = "Подключиться к домашнему серверу"; "authentication_cancel_flow_confirmation_message" = "Ваш аккаунт ещё не создан. Остановить процесс регистрации?"; "settings_timeline" = "Лента сообщений"; +"manage_session_name_info_link" = "Узнать больше"; +"user_other_session_verified_additional_info" = "Эта сессия готова к безопасному обмену сообщениями."; +"user_other_session_current_session_details" = "Текущая сессия"; +"user_other_session_filter_menu_all" = "Все сессии"; +"wysiwyg_composer_start_action_stickers" = "Наклейки"; diff --git a/Riot/Assets/sk.lproj/Vector.strings b/Riot/Assets/sk.lproj/Vector.strings index 5cecf24a5..78fe3194e 100644 --- a/Riot/Assets/sk.lproj/Vector.strings +++ b/Riot/Assets/sk.lproj/Vector.strings @@ -1361,7 +1361,7 @@ "device_verification_verify_wait_partner" = "Čakanie na potvrdenie od partnera…"; "key_verification_manually_verify_device_additional_information" = "Ak sa nezhodujú, môže byť ohrozená bezpečnosť vašej komunikácie."; "key_verification_manually_verify_device_instruction" = "Potvrďte to porovnaním nasledujúcich údajov s nastaveniami používateľa v inej relácii:"; -"key_verification_verify_sas_additional_information" = "V záujme maximálnej bezpečnosti použite iný dôveryhodný komunikačný prostriedok alebo to urobte osobne."; +"key_verification_verify_sas_additional_information" = "V záujme maximálnej bezpečnosti, použite iný dôveryhodný komunikačný prostriedok alebo to urobte osobne."; "key_verification_verify_sas_cancel_action" = "Nezhodujú sa"; "key_verification_verify_sas_title_number" = "Porovnať čísla"; "device_verification_self_verify_wait_recover_secrets_with_passphrase" = "Použiť bezpečnostnú frázu alebo kľúč"; @@ -2692,7 +2692,7 @@ "device_name_unknown" = "Neznámy klient"; "device_name_mobile" = "%@ Mobil"; "device_name_desktop" = "%@ Stolný počítač"; -"user_session_item_details" = "%@ · Posledná aktivita %@"; +"user_session_item_details" = "%1$@ · %2$@"; // First item is client name and second item is session display name "user_session_name" = "%@: %@"; @@ -2705,8 +2705,126 @@ "user_session_verified_short" = "Overené"; "user_session_unverified" = "Neoverená relácia"; "user_session_verified" = "Overená relácia"; -"user_sessions_overview_current_session_section_title" = "AKTUÁLNA RELÁCIA"; -"user_sessions_overview_other_sessions_section_info" = "V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate."; -"user_sessions_overview_other_sessions_section_title" = "OSTATNÉ RELÁCIE"; +"user_sessions_overview_current_session_section_title" = "Aktuálna relácia"; +"user_sessions_overview_other_sessions_section_info" = "V záujme čo najlepšieho zabezpečenia, overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate."; +"user_sessions_overview_other_sessions_section_title" = "Iné relácie"; "settings_labs_enable_new_app_layout" = "Nové usporiadanie aplikácie"; "room_first_message_placeholder" = "Pošlite svoju prvú správu…"; +"room_event_encryption_info_key_authenticity_not_guaranteed" = "Vierohodnosť tejto zašifrovanej správy nie je možné zaručiť na tomto zariadení."; +"user_session_overview_session_details_button_title" = "Podrobnosti o relácii"; +"user_session_overview_session_title" = "Relácia"; +"user_session_overview_current_session_title" = "Aktuálna relácia"; +"user_session_details_application_url" = "URL"; +"user_session_details_application_version" = "Verzia"; +"user_session_details_application_name" = "Názov"; +"user_session_details_device_os" = "Operačný systém"; +"user_session_details_device_browser" = "Prehliadač"; +"user_session_details_device_model" = "Model"; +"user_session_details_device_ip_location" = "Poloha IP"; +"user_session_details_device_ip_address" = "IP adresa"; +"user_session_details_session_section_footer" = "Ťuknutím na ľubovoľný údaj a jeho podržaním ho skopírujte."; +"user_session_details_session_id" = "ID relácie"; +"user_session_details_session_name" = "Názov relácie"; +"user_session_details_device_section_header" = "Zariadenie"; +"user_session_details_application_section_header" = "Aplikácia"; +"user_session_details_session_section_header" = "Relácia"; +"user_session_details_title" = "Podrobnosti o relácii"; +"user_session_push_notifications_message" = "Ak je zapnuté, táto relácia bude dostávať oznámenia push."; +"user_session_push_notifications" = "Push oznámenia"; +"user_sessions_view_all_action" = "Zobraziť všetky (%d)"; +"user_sessions_overview_security_recommendations_inactive_info" = "Zvážte odhlásenie zo starých relácií (90 dní alebo viac), ktoré už nepoužívate."; +"user_sessions_overview_security_recommendations_inactive_title" = "Neaktívne relácie"; +"user_sessions_overview_security_recommendations_unverified_info" = "Overte alebo sa odhláste z neoverených relácií."; +"user_sessions_overview_security_recommendations_unverified_title" = "Neoverené relácie"; +"user_sessions_overview_security_recommendations_section_info" = "Zlepšite zabezpečenie svojho účtu dodržiavaním týchto odporúčaní."; +"user_sessions_overview_security_recommendations_section_title" = "Bezpečnostné odporúčania"; +"all_chats_user_menu_accessibility_label" = "Používateľské menu"; +"settings_labs_enable_new_client_info_feature" = "Zaznamenať názov klienta, verziu a url, aby bolo možné ľahšie rozpoznať relácie v správcovi relácií"; +"settings_labs_enable_new_session_manager" = "Nový správca relácií"; +"device_type_name_unknown" = "Neznámy"; +"device_type_name_mobile" = "Mobil"; +"device_type_name_web" = "Web"; +"device_type_name_desktop" = "Stolný počítač"; +"user_inactive_session_item_with_date" = "Neaktívna viac ako 90 dní (%@)"; +"user_inactive_session_item" = "Neaktívna viac ako 90 dní"; +"user_other_session_unverified_sessions_header_subtitle" = "Overte si relácie pre vylepšené bezpečné zasielanie správ alebo sa odhláste z tých, ktoré už nepoznáte alebo nepoužívate."; +"user_other_session_security_recommendation_title" = "Bezpečnostné odporúčania"; +"user_sessions_overview_link_device" = "Prepojiť zariadenie"; + +// MARK: User sessions management + +// Parameter is the application display name (e.g. "Element") +"user_sessions_default_session_display_name" = "%@ iOS"; +"sign_out_confirmation_message" = "Naozaj sa chcete odhlásiť?"; + +// MARK: Sign out warning + +"sign_out" = "Odhlásiť sa"; +"manage_session_rename" = "Premenovať reláciu"; +"authentication_qr_login_failure_retry" = "Skúste to znova"; +"authentication_qr_login_failure_request_timed_out" = "Prepojenie nebolo dokončené v požadovanom čase."; +"authentication_qr_login_failure_request_denied" = "Žiadosť bola na druhom zariadení zamietnutá."; +"authentication_qr_login_failure_invalid_qr" = "QR kód nie je platný."; +"authentication_qr_login_failure_title" = "Prepojenie zlyhalo"; +"authentication_qr_login_loading_signed_in" = "Teraz ste prihlásení na svojom druhom zariadení."; +"authentication_qr_login_loading_waiting_signin" = "Čaká sa na prihlásenie zariadenia."; +"authentication_qr_login_loading_connecting_device" = "Pripájanie k zariadeniu"; +"authentication_qr_login_confirm_alert" = "Uistite sa prosím, že poznáte pôvod tohto kódu. Prepojením zariadení poskytnete niekomu plný prístup k svojmu účtu."; +"authentication_qr_login_confirm_subtitle" = "Skontrolujte, či sa nižšie uvedený kód zhoduje s vaším druhým zariadením:"; +"authentication_qr_login_confirm_title" = "Zabezpečené pripojenie bolo vytvorené"; +"authentication_qr_login_scan_subtitle" = "Umiestnite QR kód do nižšie zobrazeného štvorca"; +"authentication_qr_login_scan_title" = "Skenovať QR kód"; +"authentication_qr_login_display_step2" = "Vyberte možnosť \"Prihlásiť sa pomocou QR kódu\""; +"authentication_qr_login_display_step1" = "Otvorte aplikáciu Element na vašom druhom zariadení"; +"authentication_qr_login_display_subtitle" = "Naskenujte nižšie uvedený QR kód pomocou zariadenia, ktoré je odhlásené."; +"authentication_qr_login_display_title" = "Prepojiť zariadenie"; +"authentication_qr_login_start_display_qr" = "Zobraziť QR kód na tomto zariadení"; +"authentication_qr_login_start_need_alternative" = "Potrebujete iný spôsob?"; +"authentication_qr_login_start_step4" = "Vyberte možnosť \"Zobraziť QR kód na tomto zariadení\""; +"authentication_qr_login_start_step3" = "Vyberte položku \"Prepojiť zariadenie\""; +"authentication_qr_login_start_step2" = "Prejdite do ponuky Nastavenia -> Zabezpečenie a súkromie"; +"authentication_qr_login_start_step1" = "Otvorte aplikáciu Element na vašom druhom zariadení"; +"authentication_qr_login_start_subtitle" = "Pomocou fotoaparátu na tomto zariadení naskenujte QR kód zobrazený na vašom druhom zariadení:"; +"authentication_qr_login_start_title" = "Skenovať QR kód"; +"authentication_login_with_qr" = "Prihlásiť sa pomocou QR kódu"; +"wysiwyg_composer_format_action_strikethrough" = "Použiť formát podčiarknutia"; +"wysiwyg_composer_format_action_underline" = "Použiť formát prečiarknutia"; +"wysiwyg_composer_format_action_italic" = "Použiť formát kurzívou"; + +// Formatting Actions +"wysiwyg_composer_format_action_bold" = "Použiť tučný formát"; +"wysiwyg_composer_start_action_text_formatting" = "Formátovanie textu"; +"wysiwyg_composer_start_action_camera" = "Kamera"; +"wysiwyg_composer_start_action_location" = "Poloha"; +"wysiwyg_composer_start_action_polls" = "Ankety"; +"wysiwyg_composer_start_action_attachments" = "Prílohy"; +"wysiwyg_composer_start_action_stickers" = "Nálepky"; + + +// Mark: - WYSIWYG Composer + +// Send Media Actions +"wysiwyg_composer_start_action_media_picker" = "Knižnica fotografií"; +"user_session_details_last_activity" = "Posledná aktivita"; +"user_session_item_details_last_activity" = "Posledná aktivita %@"; +"user_other_session_clear_filter" = "Zrušiť filter"; +"user_other_session_no_unverified_sessions" = "Nenašli sa žiadne neoverené relácie."; +"user_other_session_no_verified_sessions" = "Nenašli sa žiadne overené relácie."; +"user_other_session_no_inactive_sessions" = "Nenašli sa žiadne neaktívne relácie."; +"user_other_session_filter_menu_inactive" = "Neaktívne"; +"user_other_session_filter_menu_unverified" = "Neoverené"; +"user_other_session_filter_menu_verified" = "Overené"; +"user_other_session_filter_menu_all" = "Všetky relácie"; +"user_other_session_filter" = "Filter"; +"user_other_session_verified_sessions_header_subtitle" = "V záujme čo najlepšieho zabezpečenia sa odhláste z každej relácie, ktorú už nepoznáte alebo nepoužívate."; +"user_other_session_current_session_details" = "Vaša súčasná relácia"; +"user_other_session_verified_additional_info" = "Táto relácia je pripravená na bezpečné zasielanie správ."; +"user_other_session_unverified_additional_info" = "V záujme čo najvyššej bezpečnosti a spoľahlivosti túto reláciu overte alebo sa z nej odhláste."; +"user_session_verification_unknown_additional_info" = "Overením aktuálnej relácie zistíte stav overenia tejto relácie."; +"user_session_verification_unknown_short" = "Neznámy"; +"user_session_verification_unknown" = "Neznámy stav overenia"; +"manage_session_name_info_link" = "Zistiť viac"; +/* The placeholder will be replaces with manage_session_name_info_link */ +"manage_session_name_info" = "Uvedomte si, že názvy relácií sú viditeľné aj pre ľudí, s ktorými komunikujete. %@"; +"manage_session_name_hint" = "Vlastné názvy relácií vám pomôžu ľahšie rozpoznať vaše zariadenia."; +"settings_labs_enable_wysiwyg_composer" = "Vyskúšajte rozšírený textový editor (čistý textový režim sa objaví čoskoro)"; diff --git a/Riot/Assets/sv.lproj/InfoPlist.strings b/Riot/Assets/sv.lproj/InfoPlist.strings index 246f254b8..35778cfde 100644 --- a/Riot/Assets/sv.lproj/InfoPlist.strings +++ b/Riot/Assets/sv.lproj/InfoPlist.strings @@ -1,9 +1,9 @@ -"NSPhotoLibraryUsageDescription" = "Bildbiblioteket används för att skicka bilder och videor."; +"NSPhotoLibraryUsageDescription" = "Ge åtkomst till bilder för att ladda upp bilder och videor från ditt bibliotek."; "NSCalendarsUsageDescription" = "Se dina schemalagda möten i appen."; // Permissions usage explanations -"NSCameraUsageDescription" = "Kameran används för att ta bilder och videor, och ringa videosamtal."; +"NSCameraUsageDescription" = "Kameran används för att ringa videosamtal, eller att ta och ladda upp bilder och videor."; "NSMicrophoneUsageDescription" = "Element behöver åtkomst till din mikrofon för att kunna ringa och ta emot samtal samt spela in video och röstmeddelanden."; -"NSContactsUsageDescription" = "Element kommer att visa dina kontakter så du kan bjuda in dem att chatta."; +"NSContactsUsageDescription" = "De kommer att delas med din identitetsserver för att hjälpa dig att hitta dina kontakter på Matrix."; "NSFaceIDUsageDescription" = "Face ID används för att komma åt appen."; "NSLocationWhenInUseUsageDescription" = "När du delar din plats med folk så behöver Element åtkomst för att visa dem en karta."; "NSLocationAlwaysAndWhenInUseUsageDescription" = "När du delar din plats med folk så behöver Element åtkomst för att visa dem en karta."; diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index 4d561fe3d..95706cdf6 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -1228,7 +1228,7 @@ "user_verification_session_details_information_trusted_other_user_part1" = "Цей сеанс довірений для захищеного листування, бо "; "user_verification_session_details_information_trusted_other_user_part2" = " звіряє його:"; "user_verification_session_details_information_untrusted_other_user" = " входить у новому сеансі:"; -"user_verification_session_details_additional_information_untrusted_other_user" = "Надіслані цьому сеансу й цим сеансом повідомлення позначатимуться застереженнями, поки цей користувач йому не довірить. Або ви можете власноруч звірити сеанс."; +"user_verification_session_details_additional_information_untrusted_other_user" = "Поки цей користувач не довіряє цьому сеансу, повідомлення, що надсилаються до нього і від нього, позначаються попередженнями. Крім того, ви можете звірити його вручну."; "user_verification_session_details_additional_information_untrusted_current_user" = "Якщо ви не входили в цей сеанс, ваш обліковий запис може бути під загрозою."; "user_verification_session_details_verify_action_other_user" = "Звірити власноруч"; "key_verification_bootstrap_not_setup_message" = "Спершу налаштуйте перехресне підписування."; @@ -2694,7 +2694,7 @@ "device_name_mobile" = "%@ Мобільний"; "device_name_web" = "%@ Браузер"; "device_name_desktop" = "%@ Комп'ютер"; -"user_session_item_details" = "%@ · Остання активність %@"; +"user_session_item_details" = "%1$@ · %2$@"; // First item is client name and second item is session display name "user_session_name" = "%@: %@"; @@ -2707,8 +2707,126 @@ "user_session_verified_short" = "Звірений"; "user_session_unverified" = "Не звірений сеанс"; "user_session_verified" = "Звірений сеанс"; -"user_sessions_overview_current_session_section_title" = "ПОТОЧНИЙ СЕАНС"; +"user_sessions_overview_current_session_section_title" = "Поточний сеанс"; "user_sessions_overview_other_sessions_section_info" = "Звірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте для кращої безпеки."; -"user_sessions_overview_other_sessions_section_title" = "ІНШІ СЕАНСИ"; +"user_sessions_overview_other_sessions_section_title" = "Інші сеанси"; "settings_labs_enable_new_app_layout" = "Новий вигляд застосунку"; "room_first_message_placeholder" = "Надішліть своє перше повідомлення…"; +"room_event_encryption_info_key_authenticity_not_guaranteed" = "Справжність цього зашифрованого повідомлення не може бути гарантована на цьому пристрої."; +"user_session_overview_session_details_button_title" = "Подробиці сеансу"; +"user_session_overview_session_title" = "Сеанс"; +"user_session_overview_current_session_title" = "Поточний сеанс"; +"user_session_details_application_url" = "URL"; +"user_session_details_application_version" = "Версія"; +"user_session_details_application_name" = "Назва"; +"user_session_details_device_os" = "Операційна система"; +"user_session_details_device_browser" = "Браузер"; +"user_session_details_device_model" = "Модель"; +"user_session_details_device_ip_location" = "Локація IP"; +"user_session_details_device_ip_address" = "IP-адреса"; +"user_session_details_session_section_footer" = "Копіюйте будь-які дані, затиснувши їх."; +"user_session_details_session_id" = "ID сеансу"; +"user_session_details_session_name" = "Назва сеансу"; +"user_session_details_device_section_header" = "Пристрій"; +"user_session_details_application_section_header" = "Застосунок"; +"user_session_details_session_section_header" = "Сеанс"; +"user_session_details_title" = "Подробиці сеансу"; +"user_session_push_notifications_message" = "Після ввімкнення цей сеанс отримуватиме push-сповіщення."; +"user_session_push_notifications" = "Push-сповіщення"; +"user_sessions_view_all_action" = "Переглянути всі (%d)"; +"user_sessions_overview_security_recommendations_inactive_info" = "Розгляньте можливість виходу з давніх сеансів (90 днів або давніше), якими ви більше не користуєтесь."; +"user_sessions_overview_security_recommendations_inactive_title" = "Неактивні сеанси"; +"user_sessions_overview_security_recommendations_unverified_info" = "Звірте або вийдіть з не звірених сеансів."; +"user_sessions_overview_security_recommendations_unverified_title" = "Не звірені сеанси"; +"user_sessions_overview_security_recommendations_section_info" = "Посильте безпеку свого облікового запису, дотримуючись цих порад."; +"user_sessions_overview_security_recommendations_section_title" = "Поради з безпеки"; +"all_chats_user_menu_accessibility_label" = "Меню користувача"; +"settings_labs_enable_new_client_info_feature" = "Запишіть назву клієнта, версію та URL-адресу, щоб легше розпізнавати сеанси в менеджері сеансів"; +"settings_labs_enable_new_session_manager" = "Новий менеджер сеансів"; +"device_type_name_unknown" = "Невідомо"; +"device_type_name_mobile" = "Мобільний"; +"device_type_name_web" = "Браузер"; +"device_type_name_desktop" = "Комп'ютер"; +"user_inactive_session_item_with_date" = "Неактивний понад 90 днів (%@)"; +"user_inactive_session_item" = "Неактивний понад 90 днів"; +"user_other_session_unverified_sessions_header_subtitle" = "Перевірте свої сеанси для посилення безпеки обміну повідомленнями або вийдіть з тих, які ви більше не розпізнаєте або не використовуєте."; +"user_other_session_security_recommendation_title" = "Поради з безпеки"; +"user_sessions_overview_link_device" = "Пов'язати пристрій"; + +// MARK: User sessions management + +// Parameter is the application display name (e.g. "Element") +"user_sessions_default_session_display_name" = "%@ iOS"; +"sign_out_confirmation_message" = "Точно вийти?"; + +// MARK: Sign out warning + +"sign_out" = "Вийти"; +"manage_session_rename" = "Перейменувати сеанс"; +"authentication_qr_login_failure_retry" = "Повторити спробу"; +"authentication_qr_login_failure_request_timed_out" = "У встановлені терміни з'єднання не було встановлено."; +"authentication_qr_login_failure_request_denied" = "Запит відхилено на іншому пристрої."; +"authentication_qr_login_failure_invalid_qr" = "Хибний QR-код."; +"authentication_qr_login_failure_title" = "Не вдалося під'єднати"; +"authentication_qr_login_loading_signed_in" = "Ви ввійшли на іншому пристрої."; +"authentication_qr_login_loading_waiting_signin" = "Очікування входу пристрою."; +"authentication_qr_login_loading_connecting_device" = "Під'єднання до пристрою"; +"authentication_qr_login_confirm_alert" = "Переконайтеся, що ви знаєте походження цього коду. Пов'язавши пристрої, ви надасте будь-кому повний доступ до свого облікового запису."; +"authentication_qr_login_confirm_subtitle" = "Переконайтеся, що код внизу збігається з кодом вашого іншого пристрою:"; +"authentication_qr_login_confirm_title" = "Установлено захищене з'єднання"; +"authentication_qr_login_scan_subtitle" = "Розмістіть QR-код у квадраті знизу"; +"authentication_qr_login_scan_title" = "Сканувати QR-код"; +"authentication_qr_login_display_step2" = "Виберіть «Увійти використавши QR-код»"; +"authentication_qr_login_display_step1" = "Відкрийте Element на іншому пристрої"; +"authentication_qr_login_display_subtitle" = "Зіскануйте QR-код знизу своїм пристроєм, з якого ви вийшли."; +"authentication_qr_login_display_title" = "Пов'язати пристрій"; +"authentication_qr_login_start_display_qr" = "Показати QR-код на цьому пристрої"; +"authentication_qr_login_start_need_alternative" = "Потрібен альтернативний метод?"; +"authentication_qr_login_start_step4" = "Виберіть «Показати QR-код на цьому пристрої»"; +"authentication_qr_login_start_step3" = "Виберіть «Пов'язати пристрій»"; +"authentication_qr_login_start_step2" = "Перейдіть до Налаштування -> Безпека й приватність"; +"authentication_qr_login_start_step1" = "Відкрийте Element на іншому пристрої"; +"authentication_qr_login_start_subtitle" = "Використовуйте камеру на цьому пристрої, щоб зісканувати QR-код, показаний на іншому пристрої:"; +"authentication_qr_login_start_title" = "Сканувати QR-код"; +"authentication_login_with_qr" = "Увійти використавши QR-код"; +"wysiwyg_composer_format_action_strikethrough" = "Застосувати форматування підкресленим"; +"wysiwyg_composer_format_action_underline" = "Застосувати форматування перекресленим"; + +// Formatting Actions +"wysiwyg_composer_format_action_bold" = "Застосувати форматування жирним"; +"wysiwyg_composer_format_action_italic" = "Застосувати форматування курсивом"; +"wysiwyg_composer_start_action_text_formatting" = "Форматування тексту"; +"wysiwyg_composer_start_action_camera" = "Камера"; +"wysiwyg_composer_start_action_location" = "Місце перебування"; +"wysiwyg_composer_start_action_polls" = "Опитування"; +"wysiwyg_composer_start_action_attachments" = "Вкладення"; +"wysiwyg_composer_start_action_stickers" = "Наліпки"; + + +// Mark: - WYSIWYG Composer + +// Send Media Actions +"wysiwyg_composer_start_action_media_picker" = "Фотобібліотека"; +"user_session_details_last_activity" = "Остання активність"; +"user_session_item_details_last_activity" = "Остання активність %@"; +"user_other_session_clear_filter" = "Очистити фільтр"; +"user_other_session_no_unverified_sessions" = "Не звірених сеансів не знайдено."; +"user_other_session_no_verified_sessions" = "Звірених сеансів не знайдено."; +"user_other_session_no_inactive_sessions" = "Неактивних сеансів не знайдено."; +"user_other_session_filter_menu_inactive" = "Неактивний"; +"user_other_session_filter_menu_unverified" = "Не звірений"; +"user_other_session_filter_menu_verified" = "Звірений"; +"user_other_session_filter_menu_all" = "Усі сеанси"; +"user_other_session_filter" = "Фільтр"; +"user_other_session_verified_sessions_header_subtitle" = "Для кращої безпеки виходьте з будь-якого сеансу, який ви більше не розпізнаєте або не використовуєте."; +"user_other_session_current_session_details" = "Ваш поточний сеанс"; +"user_other_session_verified_additional_info" = "Цей сеанс готовий до безпечного обміну повідомленнями."; +"user_other_session_unverified_additional_info" = "Перевірте або вийдіть з цього сеансу для кращої безпеки та надійності."; +"user_session_verification_unknown_additional_info" = "Звірте свій поточний сеанс, щоб побачити стан перевірки цього сеансу."; +"user_session_verification_unknown_short" = "Невідомо"; +"user_session_verification_unknown" = "Невідомий стан перевірки"; +"manage_session_name_info_link" = "Докладніше"; +/* The placeholder will be replaces with manage_session_name_info_link */ +"manage_session_name_info" = "Зауважте, що назви сеансів також видно людям, з якими ви спілкуєтесь. %@"; +"manage_session_name_hint" = "Власні назви сеансів допоможуть вам легше розпізнавати ваші пристрої."; +"settings_labs_enable_wysiwyg_composer" = "Спробуйте розширений текстовий редактор (незабаром з'явиться режим звичайного тексту)"; diff --git a/Riot/Categories/MXRestClient+Async.swift b/Riot/Categories/MXRestClient+Async.swift index d72bb9ce1..214c5d4da 100644 --- a/Riot/Categories/MXRestClient+Async.swift +++ b/Riot/Categories/MXRestClient+Async.swift @@ -57,6 +57,11 @@ extension MXRestClient { return MXCredentials(loginResponse: loginResponse, andDefaultCredentials: credentials) } + /// An async version of generateLoginToken(completion:) + func generateLoginToken() async throws -> MXLoginToken { + try await getResponse(generateLoginToken) + } + // MARK: - Registration /// An async version of `getRegisterSession(completion:)`. @@ -155,6 +160,15 @@ extension MXRestClient { changePassword(from: oldPassword, to: newPassword, logoutDevices: logoutDevices, completion: completion) } } + + // MARK: - Versions + + /// An async version of `supportedMatrixVersions(completion:)`. + func supportedMatrixVersions() async throws -> MXMatrixVersions { + try await getResponse({ completion in + supportedMatrixVersions(completion: completion) + }) + } // MARK: - Private diff --git a/Riot/Categories/String.swift b/Riot/Categories/String.swift index a6e48d0aa..337923493 100644 --- a/Riot/Categories/String.swift +++ b/Riot/Categories/String.swift @@ -63,6 +63,11 @@ extension String { func vc_reversed() -> String { return String(self.reversed()) } + + /// Returns nil if the string is empty or the string itself otherwise + func vc_nilIfEmpty() -> String? { + isEmpty ? nil : self + } } extension Optional where Wrapped == String { diff --git a/Riot/Categories/UIDevice.swift b/Riot/Categories/UIDevice.swift index c3405e8ed..9fabb0f82 100644 --- a/Riot/Categories/UIDevice.swift +++ b/Riot/Categories/UIDevice.swift @@ -35,7 +35,7 @@ import UIKit } var initialDisplayName: String { - isPhone ? VectorL10n.loginMobileDevice : VectorL10n.loginTabletDevice + VectorL10n.userSessionsDefaultSessionDisplayName(AppInfo.current.displayName) } } diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 006fd876c..3cc10eb2e 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -36,6 +36,7 @@ internal class Asset: NSObject { internal static let authenticationEmailIcon = ImageAsset(name: "authentication_email_icon") internal static let authenticationMsisdnIcon = ImageAsset(name: "authentication_msisdn_icon") internal static let authenticationPasswordIcon = ImageAsset(name: "authentication_password_icon") + internal static let authenticationQrloginConfirmIcon = ImageAsset(name: "authentication_qrlogin_confirm_icon") internal static let authenticationRecaptchaIcon = ImageAsset(name: "authentication_recaptcha_icon") internal static let authenticationRevealPassword = ImageAsset(name: "authentication_reveal_password") internal static let authenticationServerSelectionIcon = ImageAsset(name: "authentication_server_selection_icon") @@ -79,6 +80,7 @@ internal class Asset: NSObject { internal static let coachMark = ImageAsset(name: "coach_mark") internal static let disclosureIcon = ImageAsset(name: "disclosure_icon") internal static let errorIcon = ImageAsset(name: "error_icon") + internal static let exclamationCircle = ImageAsset(name: "exclamation_circle") internal static let faceidIcon = ImageAsset(name: "faceid_icon") internal static let filterOff = ImageAsset(name: "filter_off") internal static let filterOn = ImageAsset(name: "filter_on") @@ -98,6 +100,20 @@ internal class Asset: NSObject { internal static let touchidIcon = ImageAsset(name: "touchid_icon") internal static let addGroupParticipant = ImageAsset(name: "add_group_participant") internal static let removeIconBlue = ImageAsset(name: "remove_icon_blue") + internal static let bold = ImageAsset(name: "Bold") + internal static let code = ImageAsset(name: "Code") + internal static let indentIncrease = ImageAsset(name: "Indent_increase") + internal static let italic = ImageAsset(name: "Italic") + internal static let link = ImageAsset(name: "Link") + internal static let numberedList = ImageAsset(name: "Numbered list") + internal static let quote = ImageAsset(name: "Quote") + internal static let strikethrough = ImageAsset(name: "Strikethrough") + internal static let underlined = ImageAsset(name: "Underlined") + internal static let bulletList = ImageAsset(name: "bullet_list") + internal static let indentDecrease = ImageAsset(name: "indent_decrease") + internal static let maximiseComposer = ImageAsset(name: "maximise_composer") + internal static let minimiseComposer = ImageAsset(name: "minimise_composer") + internal static let startComposeModule = ImageAsset(name: "start_compose_module") internal static let findYourContactsFacepile = ImageAsset(name: "find_your_contacts_facepile") internal static let captureAvatar = ImageAsset(name: "capture_avatar") internal static let deleteAvatar = ImageAsset(name: "delete_avatar") @@ -105,7 +121,14 @@ internal class Asset: NSObject { internal static let deviceTypeMobile = ImageAsset(name: "device_type_mobile") internal static let deviceTypeUnknown = ImageAsset(name: "device_type_unknown") internal static let deviceTypeWeb = ImageAsset(name: "device_type_web") + internal static let userOtherSessionsFilter = ImageAsset(name: "user_other_sessions_filter") + internal static let userOtherSessionsFilterSelected = ImageAsset(name: "user_other_sessions_filter_selected") + internal static let userOtherSessionsInactive = ImageAsset(name: "user_other_sessions_inactive") + internal static let userOtherSessionsUnverified = ImageAsset(name: "user_other_sessions_unverified") + internal static let userOtherSessionsVerified = ImageAsset(name: "user_other_sessions_verified") + internal static let userSessionListItemInactiveSession = ImageAsset(name: "user_session_list_item_inactive_session") internal static let userSessionUnverified = ImageAsset(name: "user_session_unverified") + internal static let userSessionVerificationUnknown = ImageAsset(name: "user_session_verification_unknown") internal static let userSessionVerified = ImageAsset(name: "user_session_verified") internal static let userSessionsInactive = ImageAsset(name: "user_sessions_inactive") internal static let userSessionsUnverified = ImageAsset(name: "user_sessions_unverified") @@ -172,6 +195,7 @@ internal class Asset: NSObject { internal static let peopleFloatingAction = ImageAsset(name: "people_floating_action") internal static let actionCamera = ImageAsset(name: "action_camera") internal static let actionFile = ImageAsset(name: "action_file") + internal static let actionLive = ImageAsset(name: "action_live") internal static let actionLocation = ImageAsset(name: "action_location") internal static let actionMediaLibrary = ImageAsset(name: "action_media_library") internal static let actionPoll = ImageAsset(name: "action_poll") diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index f8c0cc121..7671c5c73 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -739,6 +739,110 @@ public class VectorL10n: NSObject { public static var authenticationLoginUsername: String { return VectorL10n.tr("Vector", "authentication_login_username") } + /// Sign in with QR code + public static var authenticationLoginWithQr: String { + return VectorL10n.tr("Vector", "authentication_login_with_qr") + } + /// Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account. + public static var authenticationQrLoginConfirmAlert: String { + return VectorL10n.tr("Vector", "authentication_qr_login_confirm_alert") + } + /// Confirm that the code below matches with your other device: + public static var authenticationQrLoginConfirmSubtitle: String { + return VectorL10n.tr("Vector", "authentication_qr_login_confirm_subtitle") + } + /// Secure connection established + public static var authenticationQrLoginConfirmTitle: String { + return VectorL10n.tr("Vector", "authentication_qr_login_confirm_title") + } + /// Open Element on your other device + public static var authenticationQrLoginDisplayStep1: String { + return VectorL10n.tr("Vector", "authentication_qr_login_display_step1") + } + /// Select ‘Sign in with QR code’ + public static var authenticationQrLoginDisplayStep2: String { + return VectorL10n.tr("Vector", "authentication_qr_login_display_step2") + } + /// Scan the QR code below with your device that’s signed out. + public static var authenticationQrLoginDisplaySubtitle: String { + return VectorL10n.tr("Vector", "authentication_qr_login_display_subtitle") + } + /// Link a device + public static var authenticationQrLoginDisplayTitle: String { + return VectorL10n.tr("Vector", "authentication_qr_login_display_title") + } + /// QR code is invalid. + public static var authenticationQrLoginFailureInvalidQr: String { + return VectorL10n.tr("Vector", "authentication_qr_login_failure_invalid_qr") + } + /// The request was denied on the other device. + public static var authenticationQrLoginFailureRequestDenied: String { + return VectorL10n.tr("Vector", "authentication_qr_login_failure_request_denied") + } + /// The linking wasn’t completed in the required time. + public static var authenticationQrLoginFailureRequestTimedOut: String { + return VectorL10n.tr("Vector", "authentication_qr_login_failure_request_timed_out") + } + /// Try again + public static var authenticationQrLoginFailureRetry: String { + return VectorL10n.tr("Vector", "authentication_qr_login_failure_retry") + } + /// Linking failed + public static var authenticationQrLoginFailureTitle: String { + return VectorL10n.tr("Vector", "authentication_qr_login_failure_title") + } + /// Connecting to device + public static var authenticationQrLoginLoadingConnectingDevice: String { + return VectorL10n.tr("Vector", "authentication_qr_login_loading_connecting_device") + } + /// You are now signed in on your other device. + public static var authenticationQrLoginLoadingSignedIn: String { + return VectorL10n.tr("Vector", "authentication_qr_login_loading_signed_in") + } + /// Waiting for device to sign in. + public static var authenticationQrLoginLoadingWaitingSignin: String { + return VectorL10n.tr("Vector", "authentication_qr_login_loading_waiting_signin") + } + /// Position the QR code in the square below + public static var authenticationQrLoginScanSubtitle: String { + return VectorL10n.tr("Vector", "authentication_qr_login_scan_subtitle") + } + /// Scan QR code + public static var authenticationQrLoginScanTitle: String { + return VectorL10n.tr("Vector", "authentication_qr_login_scan_title") + } + /// Show QR code on this device + public static var authenticationQrLoginStartDisplayQr: String { + return VectorL10n.tr("Vector", "authentication_qr_login_start_display_qr") + } + /// Need an alternative method? + public static var authenticationQrLoginStartNeedAlternative: String { + return VectorL10n.tr("Vector", "authentication_qr_login_start_need_alternative") + } + /// Open Element on your other device + public static var authenticationQrLoginStartStep1: String { + return VectorL10n.tr("Vector", "authentication_qr_login_start_step1") + } + /// Go to Settings -> Security & Privacy + public static var authenticationQrLoginStartStep2: String { + return VectorL10n.tr("Vector", "authentication_qr_login_start_step2") + } + /// Select ‘Link a device’ + public static var authenticationQrLoginStartStep3: String { + return VectorL10n.tr("Vector", "authentication_qr_login_start_step3") + } + /// Select ‘Show QR code on this device’ + public static var authenticationQrLoginStartStep4: String { + return VectorL10n.tr("Vector", "authentication_qr_login_start_step4") + } + /// Use the camera on this device to scan the QR code shown on your other device: + public static var authenticationQrLoginStartSubtitle: String { + return VectorL10n.tr("Vector", "authentication_qr_login_start_subtitle") + } + /// Scan QR code + public static var authenticationQrLoginStartTitle: String { + return VectorL10n.tr("Vector", "authentication_qr_login_start_title") + } /// Are you a human? public static var authenticationRecaptchaTitle: String { return VectorL10n.tr("Vector", "authentication_recaptcha_title") @@ -1471,6 +1575,22 @@ public class VectorL10n: NSObject { public static func deviceNameWeb(_ p1: String) -> String { return VectorL10n.tr("Vector", "device_name_web", p1) } + /// Desktop + public static var deviceTypeNameDesktop: String { + return VectorL10n.tr("Vector", "device_type_name_desktop") + } + /// Mobile + public static var deviceTypeNameMobile: String { + return VectorL10n.tr("Vector", "device_type_name_mobile") + } + /// Unknown + public static var deviceTypeNameUnknown: String { + return VectorL10n.tr("Vector", "device_type_name_unknown") + } + /// Web + public static var deviceTypeNameWeb: String { + return VectorL10n.tr("Vector", "device_type_name_web") + } /// The other party cancelled the verification. public static var deviceVerificationCancelled: String { return VectorL10n.tr("Vector", "device_verification_cancelled") @@ -3499,10 +3619,26 @@ public class VectorL10n: NSObject { public static var manageSessionName: String { return VectorL10n.tr("Vector", "manage_session_name") } + /// Custom session names can help you recognize your devices more easily. + public static var manageSessionNameHint: String { + return VectorL10n.tr("Vector", "manage_session_name_hint") + } + /// Please be aware that session names are also visible to people you communicate with. %@ + public static func manageSessionNameInfo(_ p1: String) -> String { + return VectorL10n.tr("Vector", "manage_session_name_info", p1) + } + /// Learn more + public static var manageSessionNameInfoLink: String { + return VectorL10n.tr("Vector", "manage_session_name_info_link") + } /// Not trusted public static var manageSessionNotTrusted: String { return VectorL10n.tr("Vector", "manage_session_not_trusted") } + /// Rename session + public static var manageSessionRename: String { + return VectorL10n.tr("Vector", "manage_session_rename") + } /// Sign out of this session public static var manageSessionSignOut: String { return VectorL10n.tr("Vector", "manage_session_sign_out") @@ -7399,6 +7535,14 @@ public class VectorL10n: NSObject { public static var settingsLabsEnableThreads: String { return VectorL10n.tr("Vector", "settings_labs_enable_threads") } + /// Voice broadcast (under active development). We currently only detect voice broadcast in the room timeline, this is not possible to send or listen an actual voice broadcast + public static var settingsLabsEnableVoiceBroadcast: String { + return VectorL10n.tr("Vector", "settings_labs_enable_voice_broadcast") + } + /// Try out the rich text editor (plain text mode coming soon) + public static var settingsLabsEnableWysiwygComposer: String { + return VectorL10n.tr("Vector", "settings_labs_enable_wysiwyg_composer") + } /// Polls public static var settingsLabsEnabledPolls: String { return VectorL10n.tr("Vector", "settings_labs_enabled_polls") @@ -7756,6 +7900,14 @@ public class VectorL10n: NSObject { return VectorL10n.tr("Vector", "side_menu_reveal_action_accessibility_label") } /// Sign out + public static var signOut: String { + return VectorL10n.tr("Vector", "sign_out") + } + /// Are you sure you want to sign out? + public static var signOutConfirmationMessage: String { + return VectorL10n.tr("Vector", "sign_out_confirmation_message") + } + /// Sign out public static var signOutExistingKeyBackupAlertSignOutAction: String { return VectorL10n.tr("Vector", "sign_out_existing_key_backup_alert_sign_out_action") } @@ -8483,6 +8635,74 @@ public class VectorL10n: NSObject { public static var userIdTitle: String { return VectorL10n.tr("Vector", "user_id_title") } + /// Inactive for 90+ days + public static var userInactiveSessionItem: String { + return VectorL10n.tr("Vector", "user_inactive_session_item") + } + /// Inactive for 90+ days (%@) + public static func userInactiveSessionItemWithDate(_ p1: String) -> String { + return VectorL10n.tr("Vector", "user_inactive_session_item_with_date", p1) + } + /// Clear filter + public static var userOtherSessionClearFilter: String { + return VectorL10n.tr("Vector", "user_other_session_clear_filter") + } + /// Your current session + public static var userOtherSessionCurrentSessionDetails: String { + return VectorL10n.tr("Vector", "user_other_session_current_session_details") + } + /// Filter + public static var userOtherSessionFilter: String { + return VectorL10n.tr("Vector", "user_other_session_filter") + } + /// All sessions + public static var userOtherSessionFilterMenuAll: String { + return VectorL10n.tr("Vector", "user_other_session_filter_menu_all") + } + /// Inactive + public static var userOtherSessionFilterMenuInactive: String { + return VectorL10n.tr("Vector", "user_other_session_filter_menu_inactive") + } + /// Unverified + public static var userOtherSessionFilterMenuUnverified: String { + return VectorL10n.tr("Vector", "user_other_session_filter_menu_unverified") + } + /// Verified + public static var userOtherSessionFilterMenuVerified: String { + return VectorL10n.tr("Vector", "user_other_session_filter_menu_verified") + } + /// No inactive sessions found. + public static var userOtherSessionNoInactiveSessions: String { + return VectorL10n.tr("Vector", "user_other_session_no_inactive_sessions") + } + /// No unverified sessions found. + public static var userOtherSessionNoUnverifiedSessions: String { + return VectorL10n.tr("Vector", "user_other_session_no_unverified_sessions") + } + /// No verified sessions found. + public static var userOtherSessionNoVerifiedSessions: String { + return VectorL10n.tr("Vector", "user_other_session_no_verified_sessions") + } + /// Security recommendation + public static var userOtherSessionSecurityRecommendationTitle: String { + return VectorL10n.tr("Vector", "user_other_session_security_recommendation_title") + } + /// Verify or sign out from this session for best security and reliability. + public static var userOtherSessionUnverifiedAdditionalInfo: String { + return VectorL10n.tr("Vector", "user_other_session_unverified_additional_info") + } + /// Verify your sessions for enhanced secure messaging or sign out from those you don’t recognize or use anymore. + public static var userOtherSessionUnverifiedSessionsHeaderSubtitle: String { + return VectorL10n.tr("Vector", "user_other_session_unverified_sessions_header_subtitle") + } + /// This session is ready for secure messaging. + public static var userOtherSessionVerifiedAdditionalInfo: String { + return VectorL10n.tr("Vector", "user_other_session_verified_additional_info") + } + /// For best security, sign out from any session that you don’t recognize or use anymore. + public static var userOtherSessionVerifiedSessionsHeaderSubtitle: String { + return VectorL10n.tr("Vector", "user_other_session_verified_sessions_header_subtitle") + } /// Name public static var userSessionDetailsApplicationName: String { return VectorL10n.tr("Vector", "user_session_details_application_name") @@ -8523,6 +8743,10 @@ public class VectorL10n: NSObject { public static var userSessionDetailsDeviceSectionHeader: String { return VectorL10n.tr("Vector", "user_session_details_device_section_header") } + /// Last activity + public static var userSessionDetailsLastActivity: String { + return VectorL10n.tr("Vector", "user_session_details_last_activity") + } /// Session ID public static var userSessionDetailsSessionId: String { return VectorL10n.tr("Vector", "user_session_details_session_id") @@ -8543,10 +8767,14 @@ public class VectorL10n: NSObject { public static var userSessionDetailsTitle: String { return VectorL10n.tr("Vector", "user_session_details_title") } - /// %@ · Last activity %@ + /// %1$@ · %2$@ public static func userSessionItemDetails(_ p1: String, _ p2: String) -> String { return VectorL10n.tr("Vector", "user_session_item_details", p1, p2) } + /// Last activity %@ + public static func userSessionItemDetailsLastActivity(_ p1: String) -> String { + return VectorL10n.tr("Vector", "user_session_item_details_last_activity", p1) + } /// Learn more public static var userSessionLearnMore: String { return VectorL10n.tr("Vector", "user_session_learn_more") @@ -8587,6 +8815,18 @@ public class VectorL10n: NSObject { public static var userSessionUnverifiedShort: String { return VectorL10n.tr("Vector", "user_session_unverified_short") } + /// Unknown verification status + public static var userSessionVerificationUnknown: String { + return VectorL10n.tr("Vector", "user_session_verification_unknown") + } + /// Verify your current session to reveal this session's verification status. + public static var userSessionVerificationUnknownAdditionalInfo: String { + return VectorL10n.tr("Vector", "user_session_verification_unknown_additional_info") + } + /// Unknown + public static var userSessionVerificationUnknownShort: String { + return VectorL10n.tr("Vector", "user_session_verification_unknown_short") + } /// Verified session public static var userSessionVerified: String { return VectorL10n.tr("Vector", "user_session_verified") @@ -8607,10 +8847,18 @@ public class VectorL10n: NSObject { public static var userSessionViewDetails: String { return VectorL10n.tr("Vector", "user_session_view_details") } + /// %@ iOS + public static func userSessionsDefaultSessionDisplayName(_ p1: String) -> String { + return VectorL10n.tr("Vector", "user_sessions_default_session_display_name", p1) + } /// Current session public static var userSessionsOverviewCurrentSessionSectionTitle: String { return VectorL10n.tr("Vector", "user_sessions_overview_current_session_section_title") } + /// Link a device + public static var userSessionsOverviewLinkDevice: String { + return VectorL10n.tr("Vector", "user_sessions_overview_link_device") + } /// For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore. public static var userSessionsOverviewOtherSessionsSectionInfo: String { return VectorL10n.tr("Vector", "user_sessions_overview_other_sessions_section_info") @@ -8915,6 +9163,50 @@ public class VectorL10n: NSObject { public static var widgetStickerPickerNoStickerpacksAlertAddNow: String { return VectorL10n.tr("Vector", "widget_sticker_picker_no_stickerpacks_alert_add_now") } + /// Apply bold format + public static var wysiwygComposerFormatActionBold: String { + return VectorL10n.tr("Vector", "wysiwyg_composer_format_action_bold") + } + /// Apply italic format + public static var wysiwygComposerFormatActionItalic: String { + return VectorL10n.tr("Vector", "wysiwyg_composer_format_action_italic") + } + /// Apply underline format + public static var wysiwygComposerFormatActionStrikethrough: String { + return VectorL10n.tr("Vector", "wysiwyg_composer_format_action_strikethrough") + } + /// Apply strikethrough format + public static var wysiwygComposerFormatActionUnderline: String { + return VectorL10n.tr("Vector", "wysiwyg_composer_format_action_underline") + } + /// Attachments + public static var wysiwygComposerStartActionAttachments: String { + return VectorL10n.tr("Vector", "wysiwyg_composer_start_action_attachments") + } + /// Camera + public static var wysiwygComposerStartActionCamera: String { + return VectorL10n.tr("Vector", "wysiwyg_composer_start_action_camera") + } + /// Location + public static var wysiwygComposerStartActionLocation: String { + return VectorL10n.tr("Vector", "wysiwyg_composer_start_action_location") + } + /// Photo Library + public static var wysiwygComposerStartActionMediaPicker: String { + return VectorL10n.tr("Vector", "wysiwyg_composer_start_action_media_picker") + } + /// Polls + public static var wysiwygComposerStartActionPolls: String { + return VectorL10n.tr("Vector", "wysiwyg_composer_start_action_polls") + } + /// Stickers + public static var wysiwygComposerStartActionStickers: String { + return VectorL10n.tr("Vector", "wysiwyg_composer_start_action_stickers") + } + /// Text Formatting + public static var wysiwygComposerStartActionTextFormatting: String { + return VectorL10n.tr("Vector", "wysiwyg_composer_start_action_text_formatting") + } /// Yes public static var yes: String { return VectorL10n.tr("Vector", "yes") diff --git a/Riot/Generated/UntranslatedStrings.swift b/Riot/Generated/UntranslatedStrings.swift index f273877eb..f571ff96d 100644 --- a/Riot/Generated/UntranslatedStrings.swift +++ b/Riot/Generated/UntranslatedStrings.swift @@ -14,6 +14,14 @@ public extension VectorL10n { static var imagePickerActionFiles: String { return VectorL10n.tr("Untranslated", "image_picker_action_files") } + /// We currently only detect voice broadcast in the room timeline, this is not possible to send or listen an actual voice broadcast + static var voiceBroadcastInTimelineBody: String { + return VectorL10n.tr("Untranslated", "voice_broadcast_in_timeline_body") + } + /// Voice broadcast detected (under active development) + static var voiceBroadcastInTimelineTitle: String { + return VectorL10n.tr("Untranslated", "voice_broadcast_in_timeline_title") + } } // swiftlint:enable function_parameter_count identifier_name line_length type_body_length diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index 523ca86a4..2e885b1cf 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -172,6 +172,14 @@ final class RiotSettings: NSObject { @UserDefault(key: "enableClientInformationFeature", defaultValue: false, storage: defaults) var enableClientInformationFeature + /// Flag indicating if the wysiwyg composer feature is enabled + @UserDefault(key: "enableWysiwygComposer", defaultValue: false, storage: defaults) + var enableWysiwygComposer + + /// Flag indicating if the voice broadcast feature is enabled + @UserDefault(key: "enableVoiceBroadcast", defaultValue: false, storage: defaults) + var enableVoiceBroadcast + // MARK: Calls /// Indicate if `allowStunServerFallback` settings has been set once. @@ -395,5 +403,4 @@ final class RiotSettings: NSObject { // MARK: - RiotSettings notification constants extension RiotSettings { public static let didUpdateLiveLocationSharingActivation = Notification.Name("RiotSettingsDidUpdateLiveLocationSharingActivation") - public static let newAppLayoutBetaToggleDidChange = Notification.Name("RiotSettingsNewAppLayoutBetaToggleDidChange") } diff --git a/Riot/Modules/Application/AppCoordinator.swift b/Riot/Modules/Application/AppCoordinator.swift index 88f2e5099..202e698a0 100755 --- a/Riot/Modules/Application/AppCoordinator.swift +++ b/Riot/Modules/Application/AppCoordinator.swift @@ -105,8 +105,6 @@ final class AppCoordinator: NSObject, AppCoordinatorType { } } - NotificationCenter.default.addObserver(self, selector: #selector(self.newAppLayoutToggleDidChange(notification:)), name: RiotSettings.newAppLayoutBetaToggleDidChange, object: nil) - // NOTE: When split view is shown there can be no Matrix sessions ready. Keep this behavior or use a loading screen before showing the split view. self.showSplitView() MXLog.debug("[AppCoordinator] Showed split view") @@ -162,12 +160,6 @@ final class AppCoordinator: NSObject, AppCoordinatorType { ThemePublisher.shared.republish(themeIdPublisher: themeIdPublisher) } - @objc private func newAppLayoutToggleDidChange(notification: Notification) { - if BuildSettings.enableSideMenu { - self.addSideMenu() - } - } - private func excludeAllItemsFromBackup() { let manager = FileManager.default diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 718669fa4..040e243f5 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -3599,7 +3599,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni usingBlock:^(NSNotification *notif) { NSObject *object = notif.userInfo[MXKeyVerificationManagerNotificationTransactionKey]; - if ([object isKindOfClass:MXIncomingSASTransaction.class]) + if ([object conformsToProtocol:@protocol(MXSASTransaction)] && ((id)object).isIncoming) { [self checkPendingIncomingKeyVerificationsInSession:mxSession]; } @@ -3630,9 +3630,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni for (id transaction in transactions) { - if (transaction.isIncoming) + if ([transaction conformsToProtocol:@protocol(MXSASTransaction)] && transaction.isIncoming) { - MXIncomingSASTransaction *incomingTransaction = (MXIncomingSASTransaction*)transaction; + id incomingTransaction = (id)transaction; if (incomingTransaction.state == MXSASTransactionStateIncomingShowAccept) { [self presentIncomingKeyVerification:incomingTransaction inSession:mxSession]; @@ -3676,7 +3676,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni return presented; } -- (BOOL)presentIncomingKeyVerification:(MXIncomingSASTransaction*)transaction inSession:(MXSession*)mxSession +- (BOOL)presentIncomingKeyVerification:(id)transaction inSession:(MXSession*)mxSession { MXLogDebug(@"[AppDelegate][MXKeyVerification] presentIncomingKeyVerification: %@", transaction); @@ -3768,14 +3768,14 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni - (void)registerNewRequestNotificationForSession:(MXSession*)session { - MXKeyVerificationManager *keyverificationManager = session.crypto.keyVerificationManager; + id keyVerificationManager = session.crypto.keyVerificationManager; - if (!keyverificationManager) + if (!keyVerificationManager) { return; } - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyVerificationNewRequestNotification:) name:MXKeyVerificationManagerNewRequestNotification object:keyverificationManager]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyVerificationNewRequestNotification:) name:MXKeyVerificationManagerNewRequestNotification object:keyVerificationManager]; } - (void)keyVerificationNewRequestNotification:(NSNotification *)notification @@ -3800,28 +3800,26 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni id keyVerificationRequest = userInfo[MXKeyVerificationManagerNotificationRequestKey]; - if ([keyVerificationRequest isKindOfClass:MXKeyVerificationByDMRequest.class]) + if (keyVerificationRequest.transport == MXKeyVerificationTransportDirectMessage) { - MXKeyVerificationByDMRequest *keyVerificationByDMRequest = (MXKeyVerificationByDMRequest*)keyVerificationRequest; - - if (!keyVerificationByDMRequest.isFromMyUser && keyVerificationByDMRequest.state == MXKeyVerificationRequestStatePending) + if (!keyVerificationRequest.isFromMyUser && keyVerificationRequest.state == MXKeyVerificationRequestStatePending) { MXKAccount *currentAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; MXSession *session = currentAccount.mxSession; - MXRoom *room = [currentAccount.mxSession roomWithRoomId:keyVerificationByDMRequest.roomId]; + MXRoom *room = [currentAccount.mxSession roomWithRoomId:keyVerificationRequest.roomId]; if (!room) { MXLogDebug(@"[AppDelegate][KeyVerification] keyVerificationRequestDidChangeNotification: Unknown room"); return; } - NSString *sender = keyVerificationByDMRequest.otherUser; + NSString *sender = keyVerificationRequest.otherUser; [room state:^(MXRoomState *roomState) { NSString *senderName = [roomState.members memberName:sender]; - [self presentNewKeyVerificationRequestAlertForSession:session senderName:senderName senderId:sender request:keyVerificationByDMRequest]; + [self presentNewKeyVerificationRequestAlertForSession:session senderName:senderName senderId:sender request:keyVerificationRequest]; }]; } } @@ -3858,7 +3856,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // This happens when they or our user do not have cross-signing enabled MXLogDebug(@"[AppDelegate][KeyVerification] keyVerificationNewRequestNotification: Device verification from other user %@:%@", keyVerificationRequest.otherUser, keyVerificationRequest.otherDevice); - NSString *myUserId = ((MXKeyVerificationByToDeviceRequest*)keyVerificationRequest).to; + NSString *myUserId = keyVerificationRequest.myUserId; NSString *userId = keyVerificationRequest.otherUser; MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:myUserId]; if (account) diff --git a/Riot/Modules/Authentication/AuthenticationCoordinator.swift b/Riot/Modules/Authentication/AuthenticationCoordinator.swift index 19316a4a1..47ed8bfab 100644 --- a/Riot/Modules/Authentication/AuthenticationCoordinator.swift +++ b/Riot/Modules/Authentication/AuthenticationCoordinator.swift @@ -336,6 +336,9 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc password = loginPassword authenticationType = .password onSessionCreated(session: session, flow: .login) + case .loggedInWithQRCode(let session, let securityCompleted): + authenticationType = .other + onSessionCreated(session: session, flow: .login, securityCompleted: securityCompleted) case .fallback: showFallback(for: .login) } @@ -522,9 +525,15 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc } /// Handles the creation of a new session following on from a successful authentication. - @MainActor private func onSessionCreated(session: MXSession, flow: AuthenticationFlow) { + @MainActor private func onSessionCreated(session: MXSession, flow: AuthenticationFlow, securityCompleted: Bool = false) { self.session = session + guard !securityCompleted else { + callback?(.didLogin(session: session, authenticationFlow: flow, authenticationType: authenticationType ?? .other)) + callback?(.didComplete) + return + } + if canPresentAdditionalScreens { showLoadingAnimation() } diff --git a/Riot/Modules/Camera/CameraAccessManager.swift b/Riot/Modules/Camera/CameraAccessManager.swift index 93cacaafc..628bbc227 100644 --- a/Riot/Modules/Camera/CameraAccessManager.swift +++ b/Riot/Modules/Camera/CameraAccessManager.swift @@ -48,6 +48,22 @@ final class CameraAccessManager { break } } + + /// Checks and requests the camera access if needed. Returns `true` if granted, otherwise `false`. + func requestCameraAccessIfNeeded() async -> Bool { + let authStatus = AVCaptureDevice.authorizationStatus(for: .video) + + switch authStatus { + case .authorized: + return true + case .notDetermined: + return await AVCaptureDevice.requestAccess(for: .video) + case .denied, .restricted: + return false + @unknown default: + return false + } + } // MARK: - Private diff --git a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift index 493c29560..141c676e2 100644 --- a/Riot/Modules/Common/SwiftUI/VectorHostingController.swift +++ b/Riot/Modules/Common/SwiftUI/VectorHostingController.swift @@ -16,6 +16,7 @@ import Foundation import SwiftUI +import Combine /** UIHostingController that applies some app-level specific configuration @@ -25,7 +26,9 @@ class VectorHostingController: UIHostingController { // MARK: Private + private let forceZeroSafeAreaInsets: Bool private var theme: Theme + private var heightSubject = CurrentValueSubject(0) // MARK: Public @@ -40,8 +43,12 @@ class VectorHostingController: UIHostingController { var enableNavigationBarScrollEdgeAppearance = false /// When non-nil, the style will be applied to the status bar. var statusBarStyle: UIStatusBarStyle? - - private let forceZeroSafeAreaInsets: Bool + /// Whether or not to publish when the height of the view changes. + var publishHeightChanges: Bool = false + /// The publisher to subscribe to if `publishHeightChanges` is enabled. + var heightPublisher: AnyPublisher { + return heightSubject.eraseToAnyPublisher() + } override var preferredStatusBarStyle: UIStatusBarStyle { statusBarStyle ?? super.preferredStatusBarStyle @@ -104,6 +111,10 @@ class VectorHostingController: UIHostingController { if #available(iOS 15.0, *) { self.view.invalidateIntrinsicContentSize() } + if publishHeightChanges { + let height = sizeThatFits(in: CGSize(width: self.view.frame.width, height: UIView.layoutFittingExpandedSize.height)).height + heightSubject.send(height) + } } override func viewSafeAreaInsetsDidChange() { diff --git a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift index a4561437a..932a52333 100644 --- a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift +++ b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift @@ -74,7 +74,7 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { } private var indicators = [UserIndicator]() - private var signOutAlertPresenter = SignOutAlertPresenter() + private var signOutFlowPresenter: SignOutFlowPresenter? // MARK: Public @@ -107,8 +107,6 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { // If start has been done once do not setup view controllers again if self.hasStartedOnce == false { - signOutAlertPresenter.delegate = self - let allChatsViewController = AllChatsViewController.instantiate() allChatsViewController.allChatsDelegate = self allChatsViewController.userIndicatorStore = UserIndicatorStore(presenter: indicatorPresenter) @@ -580,87 +578,16 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { // MARK: Sign out process private func signOut() { - guard let keyBackup = currentMatrixSession?.crypto.backup else { - return - } - - signOutAlertPresenter.present(for: keyBackup.state, - areThereKeysToBackup: keyBackup.hasKeysToBackup, - from: self.allChatsViewController, - sourceView: avatarMenuButton, - animated: true) - } - - // MARK: - SecureBackupSetupCoordinatorBridgePresenter - - private var secureBackupSetupCoordinatorBridgePresenter: SecureBackupSetupCoordinatorBridgePresenter? - private var crossSigningSetupCoordinatorBridgePresenter: CrossSigningSetupCoordinatorBridgePresenter? - - private func showSecureBackupSetupFromSignOutFlow() { - if canSetupSecureBackup { - setupSecureBackup2() - } else { - // Set up cross-signing first - setupCrossSigning(title: VectorL10n.secureKeyBackupSetupIntroTitle, - message: VectorL10n.securitySettingsUserPasswordDescription) { [weak self] result in - switch result { - case .success(let isCompleted): - if isCompleted { - self?.setupSecureBackup2() - } - case .failure: - break - } - } - } - } - - private var canSetupSecureBackup: Bool { - return currentMatrixSession?.vc_canSetupSecureBackup() ?? false - } - - private func setupSecureBackup2() { guard let session = currentMatrixSession else { + MXLog.warning("[AllChatsCoordinator] Unable to sign out due to missing current session.") return } - let secureBackupSetupCoordinatorBridgePresenter = SecureBackupSetupCoordinatorBridgePresenter(session: session, allowOverwrite: true) - secureBackupSetupCoordinatorBridgePresenter.delegate = self - secureBackupSetupCoordinatorBridgePresenter.present(from: allChatsViewController, animated: true) - self.secureBackupSetupCoordinatorBridgePresenter = secureBackupSetupCoordinatorBridgePresenter - } - - private func setupCrossSigning(title: String, message: String, completion: @escaping (Result) -> Void) { - guard let session = currentMatrixSession else { - return - } - - allChatsViewController.startActivityIndicator() - allChatsViewController.view.isUserInteractionEnabled = false + let flowPresenter = SignOutFlowPresenter(session: session, presentingViewController: toPresentable()) + flowPresenter.delegate = self - let dismissAnimation = { [weak self] in - guard let self = self else { return } - - self.allChatsViewController.stopActivityIndicator() - self.allChatsViewController.view.isUserInteractionEnabled = true - self.crossSigningSetupCoordinatorBridgePresenter?.dismiss(animated: true, completion: { - self.crossSigningSetupCoordinatorBridgePresenter = nil - }) - } - - let crossSigningSetupCoordinatorBridgePresenter = CrossSigningSetupCoordinatorBridgePresenter(session: session) - crossSigningSetupCoordinatorBridgePresenter.present(with: title, message: message, from: allChatsViewController, animated: true) { - dismissAnimation() - completion(.success(true)) - } cancel: { - dismissAnimation() - completion(.success(false)) - } failure: { error in - dismissAnimation() - completion(.failure(error)) - } - - self.crossSigningSetupCoordinatorBridgePresenter = crossSigningSetupCoordinatorBridgePresenter + flowPresenter.start(sourceView: avatarMenuButton) + self.signOutFlowPresenter = flowPresenter } // MARK: - Private methods @@ -720,42 +647,21 @@ class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { viewController.loadViewIfNeeded() return viewController } - } -// MARK: - SignOutAlertPresenterDelegate -extension AllChatsCoordinator: SignOutAlertPresenterDelegate { - - func signOutAlertPresenterDidTapSignOutAction(_ presenter: SignOutAlertPresenter) { - // Prevent user to perform user interaction in settings when sign out - // TODO: Prevent user interaction in all application (navigation controller and split view controller included) +extension AllChatsCoordinator: SignOutFlowPresenterDelegate { + func signOutFlowPresenterDidStartLoading(_ presenter: SignOutFlowPresenter) { allChatsViewController.view.isUserInteractionEnabled = false allChatsViewController.startActivityIndicator() - - AppDelegate.theDelegate().logout(withConfirmation: false) { [weak self] isLoggedOut in - self?.allChatsViewController.stopActivityIndicator() - self?.allChatsViewController.view.isUserInteractionEnabled = true - } } - func signOutAlertPresenterDidTapBackupAction(_ presenter: SignOutAlertPresenter) { - showSecureBackupSetupFromSignOutFlow() + func signOutFlowPresenterDidStopLoading(_ presenter: SignOutFlowPresenter) { + allChatsViewController.view.isUserInteractionEnabled = true + allChatsViewController.stopActivityIndicator() } -} - -// MARK: - SecureBackupSetupCoordinatorBridgePresenterDelegate -extension AllChatsCoordinator: SecureBackupSetupCoordinatorBridgePresenterDelegate { - func secureBackupSetupCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: SecureBackupSetupCoordinatorBridgePresenter) { - coordinatorBridgePresenter.dismiss(animated: true) { - self.secureBackupSetupCoordinatorBridgePresenter = nil - } - } - - func secureBackupSetupCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: SecureBackupSetupCoordinatorBridgePresenter) { - coordinatorBridgePresenter.dismiss(animated: true) { - self.secureBackupSetupCoordinatorBridgePresenter = nil - } + func signOutFlowPresenter(_ presenter: SignOutFlowPresenter, didFailWith error: Error) { + AppDelegate.theDelegate().showError(asAlert: error) } } diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 1b26bfe81..de7504084 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -439,7 +439,7 @@ class AllChatsViewController: HomeViewController { } override func shouldShowEmptyView() -> Bool { - let shouldShowEmptyView = super.shouldShowEmptyView() + let shouldShowEmptyView = super.shouldShowEmptyView() && !AllChatsLayoutSettingsManager.shared.hasAnActiveFilter if shouldShowEmptyView { self.navigationItem.searchController = nil diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift index d7b31c693..51dd86b69 100644 --- a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift @@ -240,7 +240,7 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType { } } - private func showIncoming(otherUser: MXUser, transaction: MXIncomingSASTransaction) { + private func showIncoming(otherUser: MXUser, transaction: MXSASTransaction) { let coordinator = DeviceVerificationIncomingCoordinator(session: self.session, otherUser: otherUser, transaction: transaction) coordinator.delegate = self coordinator.start() @@ -429,7 +429,7 @@ extension KeyVerificationCoordinator: KeyVerificationSelfVerifyWaitCoordinatorDe self.showVerifyByScanning(keyVerificationRequest: keyVerificationRequest, animated: true) } - func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, didAcceptIncomingSASTransaction incomingSASTransaction: MXIncomingSASTransaction) { + func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, didAcceptIncomingSASTransaction incomingSASTransaction: MXSASTransaction) { self.showVerifyBySAS(transaction: incomingSASTransaction, animated: true) } diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorBridgePresenter.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorBridgePresenter.swift index f07ea75a2..6b81e87ae 100644 --- a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorBridgePresenter.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorBridgePresenter.swift @@ -74,7 +74,7 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject { self.present(coordinator: keyVerificationCoordinator, from: viewController, animated: animated) } - func present(from viewController: UIViewController, incomingTransaction: MXIncomingSASTransaction, animated: Bool) { + func present(from viewController: UIViewController, incomingTransaction: MXSASTransaction, animated: Bool) { MXLog.debug("[KeyVerificationCoordinatorBridgePresenter] Present incoming verification from \(viewController)") diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationFlow.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationFlow.swift index 1d7d4612f..57cd6e30e 100644 --- a/Riot/Modules/KeyVerification/Common/KeyVerificationFlow.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationFlow.swift @@ -28,5 +28,5 @@ enum KeyVerificationFlow { case verifyDevice(userId: String, deviceId: String) case completeSecurity(_ isNewSignIn: Bool) case incomingRequest(_ request: MXKeyVerificationRequest) - case incomingSASTransaction(_ transaction: MXIncomingSASTransaction) + case incomingSASTransaction(_ transaction: MXSASTransaction) } diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift index fe54a2467..284ea3276 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift @@ -102,7 +102,7 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca self.update(viewState: .loaded(viewData: viewData)) - self.registerTransactionDidStateChangeNotification() + self.registerDidStateChangeNotification() } private func canShowScanAction(from verificationMethods: [String]) -> Bool { @@ -112,7 +112,7 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca private func cancel() { self.cancelQRCodeTransaction() self.keyVerificationRequest.cancel(with: MXTransactionCancelCode.user(), success: nil, failure: nil) - self.unregisterTransactionDidStateChangeNotification() + self.unregisterDidStateChangeNotification() self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCancel(self) } @@ -148,7 +148,7 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca return } - self.unregisterTransactionDidStateChangeNotification() + self.unregisterDidStateChangeNotification() self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModel(self, didScanOtherQRCodeData: scannedQRCodeData, withTransaction: qrCodeTransaction) } @@ -176,7 +176,7 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca // Check due to legacy implementation of key verification which could pass incorrect type of transaction if keyVerificationTransaction is MXIncomingSASTransaction { MXLog.debug("[KeyVerificationVerifyByScanningViewModel] SAS transaction should be outgoing") - self.unregisterTransactionDidStateChangeNotification() + self.unregisterDidStateChangeNotification() self.update(viewState: .error(KeyVerificationVerifyByScanningViewModelError.unknown)) } @@ -191,14 +191,27 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca // MARK: - MXKeyVerificationTransactionDidChange - private func registerTransactionDidStateChangeNotification() { + private func registerDidStateChangeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(requestDidStateChange(notification:)), name: .MXKeyVerificationRequestDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: .MXKeyVerificationTransactionDidChange, object: nil) } - private func unregisterTransactionDidStateChangeNotification() { + private func unregisterDidStateChangeNotification() { + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationRequestDidChange, object: nil) NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) } + @objc private func requestDidStateChange(notification: Notification) { + guard let request = notification.object as? MXKeyVerificationRequest else { + return + } + + if request.state == MXKeyVerificationRequestStateCancelled, let reason = request.reasonCancelCode { + self.unregisterDidStateChangeNotification() + self.update(viewState: .cancelled(cancelCode: reason, verificationKind: verificationKind)) + } + } + @objc private func transactionDidStateChange(notification: Notification) { guard let transaction = notification.object as? MXKeyVerificationTransaction else { return @@ -219,19 +232,19 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca private func sasTransactionDidStateChange(_ transaction: MXSASTransaction) { switch transaction.state { case MXSASTransactionStateShowSAS: - self.unregisterTransactionDidStateChangeNotification() + self.unregisterDidStateChangeNotification() self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModel(self, didStartSASVerificationWithTransaction: transaction) case MXSASTransactionStateCancelled: guard let reason = transaction.reasonCancelCode else { return } - self.unregisterTransactionDidStateChangeNotification() + self.unregisterDidStateChangeNotification() self.update(viewState: .cancelled(cancelCode: reason, verificationKind: verificationKind)) case MXSASTransactionStateCancelledByMe: guard let reason = transaction.reasonCancelCode else { return } - self.unregisterTransactionDidStateChangeNotification() + self.unregisterDidStateChangeNotification() self.update(viewState: .cancelledByMe(reason)) default: break @@ -242,22 +255,22 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca switch transaction.state { case .verified: // Should not happen - self.unregisterTransactionDidStateChangeNotification() + self.unregisterDidStateChangeNotification() self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCancel(self) case .qrScannedByOther: - self.unregisterTransactionDidStateChangeNotification() + self.unregisterDidStateChangeNotification() self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModel(self, qrCodeDidScannedByOtherWithTransaction: transaction) case .cancelled: guard let reason = transaction.reasonCancelCode else { return } - self.unregisterTransactionDidStateChangeNotification() + self.unregisterDidStateChangeNotification() self.update(viewState: .cancelled(cancelCode: reason, verificationKind: verificationKind)) case .cancelledByMe: guard let reason = transaction.reasonCancelCode else { return } - self.unregisterTransactionDidStateChangeNotification() + self.unregisterDidStateChangeNotification() self.update(viewState: .cancelledByMe(reason)) default: break diff --git a/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinator.swift b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinator.swift index 2240255d9..f55f74970 100644 --- a/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinator.swift +++ b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingCoordinator.swift @@ -38,7 +38,7 @@ final class DeviceVerificationIncomingCoordinator: DeviceVerificationIncomingCoo // MARK: - Setup - init(session: MXSession, otherUser: MXUser, transaction: MXIncomingSASTransaction) { + init(session: MXSession, otherUser: MXUser, transaction: MXSASTransaction) { self.session = session let deviceVerificationIncomingViewModel = DeviceVerificationIncomingViewModel(session: self.session, otherUser: otherUser, transaction: transaction) diff --git a/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModel.swift b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModel.swift index 69a324221..2ea84bbee 100644 --- a/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModel.swift +++ b/Riot/Modules/KeyVerification/Device/Incoming/DeviceVerificationIncomingViewModel.swift @@ -25,7 +25,7 @@ final class DeviceVerificationIncomingViewModel: DeviceVerificationIncomingViewM // MARK: Private private let session: MXSession - private let transaction: MXIncomingSASTransaction + private let transaction: MXSASTransaction // MARK: Public @@ -41,7 +41,7 @@ final class DeviceVerificationIncomingViewModel: DeviceVerificationIncomingViewM // MARK: - Setup - init(session: MXSession, otherUser: MXUser, transaction: MXIncomingSASTransaction) { + init(session: MXSession, otherUser: MXUser, transaction: MXSASTransaction) { self.session = session self.transaction = transaction self.userId = otherUser.userId @@ -83,7 +83,7 @@ final class DeviceVerificationIncomingViewModel: DeviceVerificationIncomingViewM // MARK: - MXKeyVerificationTransactionDidChange - private func registerTransactionDidStateChangeNotification(transaction: MXIncomingSASTransaction) { + private func registerTransactionDidStateChangeNotification(transaction: MXSASTransaction) { NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXKeyVerificationTransactionDidChange, object: transaction) } @@ -92,7 +92,7 @@ final class DeviceVerificationIncomingViewModel: DeviceVerificationIncomingViewM } @objc private func transactionDidStateChange(notification: Notification) { - guard let transaction = notification.object as? MXIncomingSASTransaction else { + guard let transaction = notification.object as? MXSASTransaction, transaction.isIncoming else { return } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift index 94b3a3eca..88c2537db 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift @@ -68,7 +68,7 @@ extension KeyVerificationSelfVerifyWaitCoordinator: KeyVerificationSelfVerifyWai self.delegate?.keyVerificationSelfVerifyWaitCoordinator(self, didAcceptKeyVerificationRequest: keyVerificationRequest) } - func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didAcceptIncomingSASTransaction incomingSASTransaction: MXIncomingSASTransaction) { + func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didAcceptIncomingSASTransaction incomingSASTransaction: MXSASTransaction) { self.delegate?.keyVerificationSelfVerifyWaitCoordinator(self, didAcceptIncomingSASTransaction: incomingSASTransaction) } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift index ba1b6c410..8724493ea 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinatorType.swift @@ -20,7 +20,7 @@ import Foundation protocol KeyVerificationSelfVerifyWaitCoordinatorDelegate: AnyObject { func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) - func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, didAcceptIncomingSASTransaction incomingSASTransaction: MXIncomingSASTransaction) + func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, didAcceptIncomingSASTransaction incomingSASTransaction: MXSASTransaction) func keyVerificationSelfVerifyWaitCoordinatorDidCancel(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType) func keyVerificationSelfVerifyWaitCoordinator(_ coordinator: KeyVerificationSelfVerifyWaitCoordinatorType, wantsToRecoverSecretsWith secretsRecoveryMode: SecretsRecoveryMode) } diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModel.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModel.swift index 822436f6b..29c312bfc 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModel.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModel.swift @@ -181,7 +181,7 @@ final class KeyVerificationSelfVerifyWaitViewModel: KeyVerificationSelfVerifyWai @objc private func keyVerificationManagerNewRequestNotification(notification: Notification) { - guard let userInfo = notification.userInfo, let keyVerificationRequest = userInfo[MXKeyVerificationManagerNotificationRequestKey] as? MXKeyVerificationByToDeviceRequest else { + guard let userInfo = notification.userInfo, let keyVerificationRequest = userInfo[MXKeyVerificationManagerNotificationRequestKey] as? MXKeyVerificationRequest, keyVerificationRequest.transport == .toDevice else { return } @@ -242,14 +242,14 @@ final class KeyVerificationSelfVerifyWaitViewModel: KeyVerificationSelfVerifyWai } @objc private func transactionDidStateChange(notification: Notification) { - guard let sasTransaction = notification.object as? MXIncomingSASTransaction, - sasTransaction.otherUserId == self.session.myUserId else { + guard let sasTransaction = notification.object as? MXSASTransaction, + sasTransaction.isIncoming, sasTransaction.otherUserId == self.session.myUserId else { return } self.sasTransactionDidStateChange(sasTransaction) } - private func sasTransactionDidStateChange(_ transaction: MXIncomingSASTransaction) { + private func sasTransactionDidStateChange(_ transaction: MXSASTransaction) { switch transaction.state { case MXSASTransactionStateIncomingShowAccept: transaction.accept() diff --git a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift index 264a73b83..66027c9e8 100644 --- a/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift +++ b/Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewModelType.swift @@ -24,7 +24,7 @@ protocol KeyVerificationSelfVerifyWaitViewModelViewDelegate: AnyObject { protocol KeyVerificationSelfVerifyWaitViewModelCoordinatorDelegate: AnyObject { func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) - func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didAcceptIncomingSASTransaction incomingSASTransaction: MXIncomingSASTransaction) + func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, didAcceptIncomingSASTransaction incomingSASTransaction: MXSASTransaction) func keyVerificationSelfVerifyWaitViewModelDidCancel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType) func keyVerificationSelfVerifyWaitViewModel(_ viewModel: KeyVerificationSelfVerifyWaitViewModelType, wantsToRecoverSecretsWith secretsRecoveryMode: SecretsRecoveryMode) } diff --git a/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModel.swift b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModel.swift index 694cb3474..4a8d0e66f 100644 --- a/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModel.swift +++ b/Riot/Modules/KeyVerification/Device/Start/DeviceVerificationStartViewModel.swift @@ -72,7 +72,7 @@ final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelTy guard let sself = self else { return } - guard let sasTransaction: MXOutgoingSASTransaction = transaction as? MXOutgoingSASTransaction else { + guard let sasTransaction = transaction as? MXSASTransaction, !sasTransaction.isIncoming else { return } @@ -100,7 +100,7 @@ final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelTy // MARK: - MXKeyVerificationTransactionDidChange - private func registerTransactionDidStateChangeNotification(transaction: MXOutgoingSASTransaction) { + private func registerTransactionDidStateChangeNotification(transaction: MXSASTransaction) { NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXKeyVerificationTransactionDidChange, object: transaction) } @@ -109,7 +109,7 @@ final class DeviceVerificationStartViewModel: DeviceVerificationStartViewModelTy } @objc private func transactionDidStateChange(notification: Notification) { - guard let transaction = notification.object as? MXOutgoingSASTransaction else { + guard let transaction = notification.object as? MXSASTransaction, !transaction.isIncoming else { return } diff --git a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.h b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.h index 665f45ace..fd28f9939 100644 --- a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.h +++ b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.h @@ -306,6 +306,15 @@ typedef BOOL (^MXKAccountOnCertificateChange)(MXKAccount *mxAccount, NSData *cer */ - (void)load3PIDs:(void (^)(void))success failure:(void (^)(NSError *error))failure; +/** + Loads the pusher instance linked to this account. + This method must be called to refresh self.pushNotificationServiceIsActive + + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + */ +- (void)loadCurrentPusher:(nullable void (^)(void))success failure:(nullable void (^)(NSError *error))failure; + /** Load the current device information for this account. This method must be called to refresh self.device. diff --git a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m index 5e7582808..128e9b161 100644 --- a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m +++ b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m @@ -86,6 +86,8 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; // Observe NSCurrentLocaleDidChangeNotification to refresh MXRoomSummaries on time formatting change. id NSCurrentLocaleDidChangeNotificationObserver; + + MXPusher *currentPusher; } /// Will be true if the session is not in a pauseable state or we requested for the session to pause but not finished yet. Will be reverted to false again after `resume` called. @@ -149,6 +151,7 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; // Refresh device information [self loadDeviceInformation:nil failure:nil]; + [self loadCurrentPusher:nil failure:nil]; [self registerAccountDataDidChangeIdentityServerNotification]; [self registerIdentityServiceDidChangeAccessTokenNotification]; @@ -184,6 +187,7 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; // Refresh device information [self loadDeviceInformation:nil failure:nil]; + [self loadCurrentPusher:nil failure:nil]; } return self; @@ -303,6 +307,12 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; - (BOOL)pushNotificationServiceIsActive { + if (currentPusher && currentPusher.enabled) + { + MXLogDebug(@"[MXKAccount][Push] pushNotificationServiceIsActive: currentPusher.enabled %@", currentPusher.enabled); + return currentPusher.enabled.boolValue; + } + BOOL pushNotificationServiceIsActive = ([[MXKAccountManager sharedManager] isAPNSAvailable] && self.hasPusherForPushNotifications && mxSession); MXLogDebug(@"[MXKAccount][Push] pushNotificationServiceIsActive: %@", @(pushNotificationServiceIsActive)); @@ -317,7 +327,44 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; if (enable) { - if ([[MXKAccountManager sharedManager] isAPNSAvailable]) + if (currentPusher && currentPusher.enabled && !currentPusher.enabled.boolValue) + { + [self.mxSession.matrixRestClient setPusherWithPushkey:currentPusher.pushkey + kind:currentPusher.kind + appId:currentPusher.appId + appDisplayName:currentPusher.appDisplayName + deviceDisplayName:currentPusher.deviceDisplayName + profileTag:currentPusher.profileTag + lang:currentPusher.lang + data:currentPusher.data.JSONDictionary + append:NO + enabled:enable + success:^{ + + MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: remotely enabled Push: Success"); + [self loadCurrentPusher:^{ + if (success) + { + success(); + } + } failure:^(NSError *error) { + + MXLogWarning(@"[MXKAccount][Push] enablePushNotifications: load current pusher failed with error: %@", error); + if (failure) + { + failure(error); + } + }]; + } failure:^(NSError *error) { + + MXLogWarning(@"[MXKAccount][Push] enablePushNotifications: remotely enable push failed with error: %@", error); + if (failure) + { + failure(error); + } + }]; + } + else if ([[MXKAccountManager sharedManager] isAPNSAvailable]) { MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: Enable Push for %@ account", self.mxCredentials.userId); @@ -354,7 +401,7 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; } } } - else if (self.hasPusherForPushNotifications) + else if (self.hasPusherForPushNotifications || currentPusher) { MXLogDebug(@"[MXKAccount][Push] enablePushNotifications: Disable APNS for %@ account", self.mxCredentials.userId); @@ -626,6 +673,65 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; }]; } +- (void)loadCurrentPusher:(void (^)(void))success failure:(void (^)(NSError *error))failure +{ + if (!self.mxSession.myDeviceId) + { + MXLogWarning(@"[MXKAccount] loadPusher: device ID not found"); + if (failure) + { + failure([NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:nil]); + } + return; + } + + [self.mxSession supportedMatrixVersions:^(MXMatrixVersions *matrixVersions) { + if (!matrixVersions.supportsRemotelyTogglingPushNotifications) + { + MXLogDebug(@"[MXKAccount] loadPusher: remotely toggling push notifications not supported"); + + if (success) + { + success(); + } + + return; + } + + [self.mxSession.matrixRestClient pushers:^(NSArray *pushers) { + MXPusher *ownPusher; + for (MXPusher *pusher in pushers) + { + if ([pusher.deviceId isEqualToString:self.mxSession.myDeviceId]) + { + ownPusher = pusher; + } + } + + self->currentPusher = ownPusher; + + if (success) + { + success(); + } + } failure:^(NSError *error) { + MXLogWarning(@"[MXKAccount] loadPusher: get pushers failed due to error %@", error); + + if (failure) + { + failure(error); + } + }]; + } failure:^(NSError *error) { + MXLogWarning(@"[MXKAccount] loadPusher: supportedMatrixVersions failed due to error %@", error); + + if (failure) + { + failure(error); + } + }]; +} + - (void)loadDeviceInformation:(void (^)(void))success failure:(void (^)(NSError *error))failure { if (self.mxCredentials.deviceId) @@ -773,7 +879,9 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; [MXKContactManager.sharedManager validateSyncLocalContactsStateForSession:self.mxSession]; // Refresh pusher state - [self refreshAPNSPusher]; + [self loadCurrentPusher:^{ + [self refreshAPNSPusher]; + } failure:nil]; [self refreshPushKitPusher]; // Launch server sync @@ -1106,6 +1214,12 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; - (void)refreshAPNSPusher { MXLogDebug(@"[MXKAccount][Push] refreshAPNSPusher"); + + if (currentPusher) + { + MXLogDebug(@"[MXKAccount][Push] refreshAPNSPusher aborted as a pusher has been found"); + return; + } // Check the conditions required to run the pusher if (self.pushNotificationServiceIsActive) @@ -1165,12 +1279,35 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; self->_hasPusherForPushNotifications = enabled; [[MXKAccountManager sharedManager] saveAccounts]; - if (success) + if (enabled) { - success(); + [self loadCurrentPusher:^{ + if (success) + { + success(); + } + + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountAPNSActivityDidChangeNotification object:self.mxCredentials.userId]; + } failure:^(NSError *error) { + if (success) + { + success(); + } + + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountAPNSActivityDidChangeNotification object:self.mxCredentials.userId]; + }]; + } + else + { + self->currentPusher = nil; + + if (success) + { + success(); + } + + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountAPNSActivityDidChangeNotification object:self.mxCredentials.userId]; } - - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountAPNSActivityDidChangeNotification object:self.mxCredentials.userId]; } failure:^(NSError *error) { @@ -1415,7 +1552,7 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; MXRestClient *restCli = self.mxRestClient; - [restCli setPusherWithPushkey:b64Token kind:kind appId:appId appDisplayName:appDisplayName deviceDisplayName:[[UIDevice currentDevice] name] profileTag:profileTag lang:deviceLang data:pushData append:append success:success failure:failure]; + [restCli setPusherWithPushkey:b64Token kind:kind appId:appId appDisplayName:appDisplayName deviceDisplayName:[[UIDevice currentDevice] name] profileTag:profileTag lang:deviceLang data:pushData append:append enabled:enabled success:success failure:failure]; } #pragma mark - InApp notifications diff --git a/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.h b/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.h index 503441a52..2e71969ea 100644 --- a/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.h +++ b/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.h @@ -92,6 +92,22 @@ typedef enum : NSUInteger */ - (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView sendTextMessage:(NSString*)textMessage; +/** + Tells the delegate that the user wants to send a formatted text message. + + @param toolbarView the room input toolbar view. + @param formattedTextMessage the formatted message to send. + @param rawText the raw message to send. + */ +- (void)roomInputToolbarView:(MXKRoomInputToolbarView *)toolbarView sendFormattedTextMessage:(NSString *)formattedTextMessage withRawText:(NSString *)rawText; + +/** + Tells the delegate that the user wants to display the send media actions. + + @param toolbarView the room input toolbar view. + */ +- (void)roomInputToolbarViewShowSendMediaActions:(MXKRoomInputToolbarView *)toolbarView; + /** Tells the delegate that the user wants to send an image. @@ -222,7 +238,7 @@ typedef enum : NSUInteger @discussion This is the designated initializer for programmatic instantiation. @return An initialized `MXKRoomInputToolbarView-inherited` object if successful, `nil` otherwise. */ -+ (instancetype)roomInputToolbarView; ++ (MXKRoomInputToolbarView *)instantiateRoomInputToolbarView; /** The delegate notified when inputs are ready. @@ -333,7 +349,7 @@ typedef enum : NSUInteger actually used to retrieve the keyboard view. Indeed the keyboard view is the superview of the accessory view when the message composer become the first responder. */ -@property (readonly) UIView *inputAccessoryViewForKeyboard; +@property UIView *inputAccessoryViewForKeyboard; /** Display the keyboard. diff --git a/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.m b/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.m index 2b9b06691..9581df2a7 100644 --- a/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.m +++ b/Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.m @@ -69,7 +69,7 @@ bundle:[NSBundle bundleForClass:[MXKRoomInputToolbarView class]]]; } -+ (instancetype)roomInputToolbarView ++ (MXKRoomInputToolbarView *)instantiateRoomInputToolbarView { if ([[self class] nib]) { diff --git a/Riot/Modules/QRCode/QRCodeGenerator.swift b/Riot/Modules/QRCode/QRCodeGenerator.swift index 4ac8e6f6e..982722dac 100644 --- a/Riot/Modules/QRCode/QRCodeGenerator.swift +++ b/Riot/Modules/QRCode/QRCodeGenerator.swift @@ -16,13 +16,17 @@ import Foundation import ZXingObjC +import UIKit final class QRCodeGenerator { enum Error: Swift.Error { case cannotCreateImage } - func generateCode(from data: Data, with size: CGSize) throws -> UIImage { + func generateCode(from data: Data, + with size: CGSize, + onColor: UIColor = .black, + offColor: UIColor = .white) throws -> UIImage { let writer = ZXMultiFormatWriter() let endodedString = String(data: data, encoding: .isoLatin1) let scale = UIScreen.main.scale @@ -33,8 +37,10 @@ final class QRCodeGenerator { height: Int32(size.height * scale), hints: ZXEncodeHints() ) - - guard let cgImage = ZXImage(matrix: bitMatrix).cgimage else { + + guard let cgImage = ZXImage(matrix: bitMatrix, + on: onColor.cgColor, + offColor: offColor.cgColor).cgimage else { throw Error.cannotCreateImage } diff --git a/Riot/Modules/Rendezvous/MockRendezvousTransport.swift b/Riot/Modules/Rendezvous/MockRendezvousTransport.swift new file mode 100644 index 000000000..bdea728f6 --- /dev/null +++ b/Riot/Modules/Rendezvous/MockRendezvousTransport.swift @@ -0,0 +1,61 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation + +class MockRendezvousTransport: RendezvousTransportProtocol { + var rendezvousURL: URL? + + private var currentPayload: Data? + + func create(body: T) async -> Result<(), RendezvousTransportError> { + guard let url = URL(string: "rendezvous.mock/1234") else { + fatalError() + } + + rendezvousURL = url + + guard let encodedBody = try? JSONEncoder().encode(body) else { + fatalError() + } + + currentPayload = encodedBody + + return .success(()) + } + + func get() async -> Result { + guard let data = currentPayload else { + fatalError() + } + + return .success(data) + } + + func send(body: T) async -> Result<(), RendezvousTransportError> { + guard let encodedBody = try? JSONEncoder().encode(body) else { + fatalError() + } + + currentPayload = encodedBody + + return .success(()) + } + + func tearDown() async -> Result<(), RendezvousTransportError> { + return .success(()) + } +} diff --git a/Riot/Modules/Rendezvous/RendezvousService.swift b/Riot/Modules/Rendezvous/RendezvousService.swift new file mode 100644 index 000000000..49c3486bb --- /dev/null +++ b/Riot/Modules/Rendezvous/RendezvousService.swift @@ -0,0 +1,274 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation +import CryptoKit +import Combine +import MatrixSDK + +enum RendezvousServiceError: Error { + case invalidInterlocutorKey + case decodingError + case internalError + case channelNotReady + case transportError(RendezvousTransportError) +} + +/// Algorithm name as per MSC3903 +enum RendezvousChannelAlgorithm: String { + case ECDH_V1 = "org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256" +} + +/// Allows communication through a secure channel. Based on MSC3886 and MSC3903 +@MainActor +class RendezvousService { + private let transport: RendezvousTransportProtocol + + private var privateKey: Curve25519.KeyAgreement.PrivateKey! + private var interlocutorPublicKey: Curve25519.KeyAgreement.PublicKey? + private var symmetricKey: SymmetricKey? + + init(transport: RendezvousTransportProtocol) { + self.transport = transport + } + + /// Creates a new rendezvous endpoint and publishes the creator's public key + func createRendezvous() async -> Result { + privateKey = Curve25519.KeyAgreement.PrivateKey() + + let publicKeyString = privateKey.publicKey.rawRepresentation.base64EncodedString() + let details = RendezvousDetails(algorithm: RendezvousChannelAlgorithm.ECDH_V1.rawValue) + + switch await transport.create(body: details) { + case .failure(let transportError): + return .failure(.transportError(transportError)) + case .success: + guard let rendezvousURL = transport.rendezvousURL else { + return .failure(.transportError(.rendezvousURLInvalid)) + } + + let fullDetails = RendezvousDetails(algorithm: RendezvousChannelAlgorithm.ECDH_V1.rawValue, + transport: RendezvousTransportDetails(type: "org.matrix.msc3886.http.v1", + uri: rendezvousURL.absoluteString), + key: publicKeyString) + return .success(fullDetails) + } + } + + /// After creation we need to wait for the pair to publish its public key as well + /// At the end of this a symmetric key will be available for encryption + func waitForInterlocutor() async -> Result { + switch await transport.get() { + case .failure(let error): + return .failure(.transportError(error)) + case .success(let data): + guard let response = try? JSONDecoder().decode(RendezvousDetails.self, from: data) else { + return .failure(.decodingError) + } + + guard let key = response.key, + let interlocutorPublicKeyData = Data(base64Encoded: key), + let interlocutorPublicKey = try? Curve25519.KeyAgreement.PublicKey(rawRepresentation: interlocutorPublicKeyData) else { + return .failure(.invalidInterlocutorKey) + } + + self.interlocutorPublicKey = interlocutorPublicKey + + guard let sharedSecret = try? privateKey.sharedSecretFromKeyAgreement(with: interlocutorPublicKey) else { + return .failure(.internalError) + } + + self.symmetricKey = generateSymmetricKeyFrom(sharedSecret: sharedSecret, + initiatorPublicKey: privateKey.publicKey, + recipientPublicKey: interlocutorPublicKey) + + let validationCode = generateValidationCodeFrom(symmetricKey: generateSymmetricKeyFrom(sharedSecret: sharedSecret, + initiatorPublicKey: privateKey.publicKey, + recipientPublicKey: interlocutorPublicKey, + byteCount: 5)) + + return .success(validationCode) + } + } + + /// Joins an existing rendezvous and publishes the joiner's public key + /// At the end of this a symmetric key will be available for encryption + func joinRendezvous(withPublicKey publicKey: String) async -> Result { + guard let interlocutorPublicKeyData = Data(base64Encoded: publicKey), + let interlocutorPublicKey = try? Curve25519.KeyAgreement.PublicKey(rawRepresentation: interlocutorPublicKeyData) else { + MXLog.debug("[RendezvousService] Invalid interlocutor data") + return .failure(.invalidInterlocutorKey) + } + + privateKey = Curve25519.KeyAgreement.PrivateKey() + + let publicKeyString = privateKey.publicKey.rawRepresentation.base64EncodedString() + let payload = RendezvousDetails(algorithm: RendezvousChannelAlgorithm.ECDH_V1.rawValue, + key: publicKeyString) + + guard case .success = await transport.send(body: payload) else { + return .failure(.internalError) + } + + self.interlocutorPublicKey = interlocutorPublicKey + + guard let sharedSecret = try? privateKey.sharedSecretFromKeyAgreement(with: interlocutorPublicKey) else { + MXLog.debug("[RendezvousService] Couldn't create shared secret") + return .failure(.internalError) + } + + symmetricKey = generateSymmetricKeyFrom(sharedSecret: sharedSecret, + initiatorPublicKey: interlocutorPublicKey, + recipientPublicKey: privateKey.publicKey) + + let validationCode = generateValidationCodeFrom(symmetricKey: generateSymmetricKeyFrom(sharedSecret: sharedSecret, + initiatorPublicKey: interlocutorPublicKey, + recipientPublicKey: privateKey.publicKey, + byteCount: 5)) + + return .success(validationCode) + } + + /// Send arbitrary data over the secure channel + /// This will use the previously generated symmetric key to AES encrypt the payload + /// - Parameter data: the data to be encrypted and sent + /// - Returns: nothing if succeeded or a RendezvousServiceError failure + func send(data: Data) async -> Result<(), RendezvousServiceError> { + guard let symmetricKey = symmetricKey else { + return .failure(.channelNotReady) + } + + // Generate a custom random 256 bit nonce/iv as per MSC3903. The default one is 96 bit. + guard let nonce = try? AES.GCM.Nonce(data: generateRandomData(ofLength: 32)), + let sealedBox = try? AES.GCM.seal(data, using: symmetricKey, nonce: nonce) else { + return .failure(.internalError) + } + + // The resulting cipher text needs to contain both the message and the authentication tag + // in order to play nicely with other platforms + var ciphertext = sealedBox.ciphertext + ciphertext.append(contentsOf: sealedBox.tag) + + let body = RendezvousMessage(iv: Data(nonce).base64EncodedString(), + ciphertext: ciphertext.base64EncodedString()) + + switch await transport.send(body: body) { + case .failure(let transportError): + return .failure(.transportError(transportError)) + case .success: + return .success(()) + } + } + + + /// Waits for and returns newly available rendezvous channel data + /// - Returns: The unencrypted data or a RendezvousServiceError + func receive() async -> Result { + guard let symmetricKey = symmetricKey else { + return .failure(.channelNotReady) + } + + switch await transport.get() { + case.failure(let transportError): + return .failure(.transportError(transportError)) + case .success(let data): + guard let response = try? JSONDecoder().decode(RendezvousMessage.self, from: data) else { + return .failure(.decodingError) + } + + MXLog.debug("Received rendezvous response: \(response)") + + guard let ciphertextData = Data(base64Encoded: response.ciphertext), + let nonceData = Data(base64Encoded: response.iv), + let nonce = try? AES.GCM.Nonce(data: nonceData) else { + return .failure(.decodingError) + } + + // Split the ciphertext into the message and authentication tag data + let messageData = ciphertextData.dropLast(16) // The last 16 bytes are the tag + let tagData = ciphertextData.dropFirst(messageData.count) + + guard let sealedBox = try? AES.GCM.SealedBox(nonce: nonce, ciphertext: messageData, tag: tagData), + let messageData = try? AES.GCM.open(sealedBox, using: symmetricKey) else { + return .failure(.decodingError) + } + + return .success(messageData) + } + } + + func tearDown() async -> Result<(), RendezvousServiceError> { + switch await transport.tearDown() { + case .failure(let error): + return .failure(.transportError(error)) + case .success: + privateKey = nil + interlocutorPublicKey = nil + symmetricKey = nil + + return .success(()) + } + } + + // MARK: - Private + + private func generateValidationCodeFrom(symmetricKey: SymmetricKey) -> String { + let bytes = symmetricKey.withUnsafeBytes { + return Data(Array($0)) + }.map { UInt($0) } + + let first = (bytes[0] << 5 | bytes[1] >> 3) + 1000 + let secondPart1 = UInt(bytes[1] & 0x7) << 10 + let secondPart2 = bytes[2] << 2 | bytes[3] >> 6 + let second = (secondPart1 | secondPart2) + 1000 + let third = ((bytes[3] & 0x3f) << 7 | bytes[4] >> 1) + 1000 + + return "\(first)-\(second)-\(third)" + } + + private func generateSymmetricKeyFrom(sharedSecret: SharedSecret, + initiatorPublicKey: Curve25519.KeyAgreement.PublicKey, + recipientPublicKey: Curve25519.KeyAgreement.PublicKey, + byteCount: Int = SHA256Digest.byteCount) -> SymmetricKey { + guard let sharedInfoData = [RendezvousChannelAlgorithm.ECDH_V1.rawValue, + initiatorPublicKey.rawRepresentation.base64EncodedString(), + recipientPublicKey.rawRepresentation.base64EncodedString()] + .joined(separator: "|") + .data(using: .utf8) else { + fatalError("[RendezvousService] Failed creating symmetric key shared data") + } + + // MSC3903 asks for a 8 zero byte salt when deriving the keys + let salt = Data(repeating: 0, count: 8) + return sharedSecret.hkdfDerivedSymmetricKey(using: SHA256.self, + salt: salt, + sharedInfo: sharedInfoData, + outputByteCount: byteCount) + } + + private func generateRandomData(ofLength length: Int) -> Data { + var data = Data(count: length) + _ = data.withUnsafeMutableBytes { pointer -> Int32 in + if let baseAddress = pointer.baseAddress { + return SecRandomCopyBytes(kSecRandomDefault, length, baseAddress) + } + + return 0 + } + + return data + } +} diff --git a/Riot/Modules/Rendezvous/RendezvousTransport.swift b/Riot/Modules/Rendezvous/RendezvousTransport.swift new file mode 100644 index 000000000..9c4c299ab --- /dev/null +++ b/Riot/Modules/Rendezvous/RendezvousTransport.swift @@ -0,0 +1,191 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation +import MatrixSDK + +class RendezvousTransport: RendezvousTransportProtocol { + private let baseURL: URL + + private var currentEtag: String? + + private(set) var rendezvousURL: URL? { + didSet { + self.currentEtag = nil + } + } + + init(baseURL: URL, rendezvousURL: URL? = nil) { + self.baseURL = baseURL + self.rendezvousURL = rendezvousURL + } + + func get() async -> Result { + // Keep trying until resource changed + while true { + guard let url = rendezvousURL else { + return .failure(.rendezvousURLInvalid) + } + + MXLog.debug("[RendezvousTransport] polling \(url) after etag: \(String(describing: currentEtag))") + + var request = URLRequest(url: url) + request.httpMethod = "GET" + + request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData + if let etag = currentEtag { + request.addValue(etag, forHTTPHeaderField: "If-None-Match") + } + + // Newer swift concurrency api unavailable due to iOS 14 support + let result: Result = await withCheckedContinuation { continuation in + URLSession.shared.dataTask(with: request) { data, response, error in + guard error == nil, + let data = data, + let httpURLResponse = response as? HTTPURLResponse else { + continuation.resume(returning: .failure(.networkError)) + return + } + + // Return empty data from here if unchanged so that the external while can continue + if httpURLResponse.statusCode == 404 { + MXLog.warning("[RendezvousTransport] Rendezvous no longer available") + continuation.resume(returning: .failure(.rendezvousCancelled)) + } else if httpURLResponse.statusCode == 304 { + MXLog.debug("[RendezvousTransport] Rendezvous unchanged") + continuation.resume(returning: .success(nil)) + } else if httpURLResponse.statusCode == 200 { + // The resouce changed, update the etag + if let etag = httpURLResponse.allHeaderFields["Etag"] as? String { + self.currentEtag = etag + } + + MXLog.debug("[RendezvousTransport] Received update") + + continuation.resume(returning: .success(data)) + } + } + .resume() + } + + switch result { + case .failure(let error): + return .failure(error) + case .success(let data): + guard let data = data else { + // Avoid making too many requests. Sleep for one second before the next attempt + try? await Task.sleep(nanoseconds: 1_000_000_000) + + continue + } + + return .success(data) + } + } + } + + func create(body: T) async -> Result<(), RendezvousTransportError> { + switch await send(body: body, url: baseURL, usingMethod: "POST") { + case .failure(let error): + return .failure(error) + case .success(let response): + guard let rendezvousIdentifier = response.allHeaderFields["Location"] as? String else { + return .failure(.networkError) + } + + rendezvousURL = baseURL.appendingPathComponent(rendezvousIdentifier) + + return .success(()) + } + } + + func send(body: T) async -> Result<(), RendezvousTransportError> { + guard let url = rendezvousURL else { + return .failure(.rendezvousURLInvalid) + } + + switch await send(body: body, url: url, usingMethod: "PUT") { + case .failure(let error): + return .failure(error) + case .success: + return .success(()) + } + } + + func tearDown() async -> Result<(), RendezvousTransportError> { + guard let url = rendezvousURL else { + return .failure(.rendezvousURLInvalid) + } + + var request = URLRequest(url: url) + request.httpMethod = "DELETE" + return await withCheckedContinuation { continuation in + URLSession.shared.dataTask(with: request) { [weak self] data, response, error in + guard error == nil, response as? HTTPURLResponse != nil else { + MXLog.warning("[RendezvousTransport] Failed tearing down rendezvous with error: \(String(describing: error))") + continuation.resume(returning: .failure(.networkError)) + return + } + + MXLog.debug("[RendezvousTransport] Tore down rendezvous at URL: \(url)") + + self?.rendezvousURL = nil + + continuation.resume(returning: .success(())) + } + .resume() + } + } + + // MARK: - Private + + private func send(body: T, url: URL, usingMethod method: String) async -> Result { + guard let bodyData = try? JSONEncoder().encode(body) else { + return .failure(.encodingError) + } + + var request = URLRequest(url: url) + request.httpMethod = method + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("application/json", forHTTPHeaderField: "Accept") + + request.httpBody = bodyData + + request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData + if let etag = currentEtag { + request.addValue(etag, forHTTPHeaderField: "If-Match") + } + + return await withCheckedContinuation { continuation in + URLSession.shared.dataTask(with: request) { data, response, error in + guard error == nil, let httpURLResponse = response as? HTTPURLResponse else { + MXLog.warning("[RendezvousTransport] Failed sending data with error: \(String(describing: error))") + continuation.resume(returning: .failure(.networkError)) + return + } + + if let etag = httpURLResponse.allHeaderFields["Etag"] as? String { + self.currentEtag = etag + } + + MXLog.debug("[RendezvousTransport] Sent data: \(body)") + + continuation.resume(returning: .success(httpURLResponse)) + } + .resume() + } + } +} diff --git a/Riot/Modules/Rendezvous/RendezvousTransportProtocol.swift b/Riot/Modules/Rendezvous/RendezvousTransportProtocol.swift new file mode 100644 index 000000000..82a8be0d0 --- /dev/null +++ b/Riot/Modules/Rendezvous/RendezvousTransportProtocol.swift @@ -0,0 +1,46 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation + +enum RendezvousTransportError: Error { + case rendezvousURLInvalid + case encodingError + case networkError + case rendezvousCancelled +} + +/// HTTP based MSC3886 channel implementation +@MainActor +protocol RendezvousTransportProtocol { + /// The current rendezvous endpoint. + /// Automatically assigned after a successful creation + var rendezvousURL: URL? { get } + + /// Creates a new rendezvous point containing the body + /// - Parameter body: arbitrary data to publish on the rendevous + /// - Returns:a transport error in case of failure + func create(body: T) async -> Result<(), RendezvousTransportError> + + /// Waits for and returns newly availalbe rendezvous data + func get() async -> Result + + /// Publishes new rendezvous data + func send(body: T) async -> Result<(), RendezvousTransportError> + + /// Deletes the resource at the current rendezvous endpoint + func tearDown() async -> Result<(), RendezvousTransportError> +} diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.h b/Riot/Modules/Room/CellData/RoomBubbleCellData.h index c72ad7903..cc76d4880 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.h +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.h @@ -36,7 +36,8 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) RoomBubbleCellDataTagRoomCreationIntro, RoomBubbleCellDataTagPoll, RoomBubbleCellDataTagLocation, - RoomBubbleCellDataTagLiveLocation + RoomBubbleCellDataTagLiveLocation, + RoomBubbleCellDataTagVoiceBroadcast }; /** diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index 35f306f11..301b87328 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -183,16 +183,32 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat self.displayTimestampForSelectedComponentOnLeftWhenPossible = NO; } } + else if ([event.type isEqualToString:VoiceBroadcastSettings.eventType]) + { + self.tag = RoomBubbleCellDataTagVoiceBroadcast; + self.collapsable = NO; + self.collapsed = NO; + + MXLogDebug(@"VB incoming initWithEvent") + break; + } break; } case MXEventTypeRoomMessage: { - if (event.location) { + if (event.location) + { self.tag = RoomBubbleCellDataTagLocation; self.collapsable = NO; self.collapsed = NO; } + else if (event.content[VoiceBroadcastSettings.voiceBroadcastContentKeyChunkType]) + { + self.tag = RoomBubbleCellDataTagVoiceBroadcast; + self.collapsable = NO; + self.collapsed = NO; + } break; } @@ -271,42 +287,48 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat - (BOOL)hasNoDisplay { - if (self.tag == RoomBubbleCellDataTagKeyVerificationNoDisplay) + BOOL hasNoDisplay = YES; + + switch (self.tag) { - return YES; + case RoomBubbleCellDataTagKeyVerificationNoDisplay: + hasNoDisplay = YES; + break; + case RoomBubbleCellDataTagRoomCreationIntro: + hasNoDisplay = NO; + break; + case RoomBubbleCellDataTagPoll: + if (!self.events.lastObject.isEditEvent) + { + hasNoDisplay = NO; + } + + break; + case RoomBubbleCellDataTagLocation: + hasNoDisplay = NO; + break; + case RoomBubbleCellDataTagLiveLocation: + // Show the cell only if the summary exists + if (self.beaconInfoSummary) + { + hasNoDisplay = NO; + } + + break; + case RoomBubbleCellDataTagVoiceBroadcast: + if (RiotSettings.shared.enableVoiceBroadcast == YES && + [VoiceBroadcastInfo isStartedFor:[VoiceBroadcastInfo modelFromJSON:self.events.lastObject.content].state]) + { + hasNoDisplay = NO; + } + + break; + default: + hasNoDisplay = [super hasNoDisplay]; + break; } - if (self.tag == RoomBubbleCellDataTagRoomCreationIntro) - { - return NO; - } - - if (self.tag == RoomBubbleCellDataTagPoll) - { - if (self.events.lastObject.isEditEvent) { - return YES; - } - - return NO; - } - - if (self.tag == RoomBubbleCellDataTagLocation) - { - return NO; - } - - if (self.tag == RoomBubbleCellDataTagLiveLocation) - { - // If the summary does not exist don't show the cell - if (!self.beaconInfoSummary) - { - return YES; - } - - return NO; - } - - return [super hasNoDisplay]; + return hasNoDisplay; } - (BOOL)hasThreadRoot @@ -1050,6 +1072,9 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat case RoomBubbleCellDataTagLiveLocation: shouldAddEvent = NO; break; + case RoomBubbleCellDataTagVoiceBroadcast: + shouldAddEvent = NO; + break; default: break; } @@ -1118,6 +1143,8 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat { shouldAddEvent = NO; } + } else if ([event.type isEqualToString:VoiceBroadcastSettings.eventType]) { + shouldAddEvent = NO; } break; } diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index ed48741db..842623818 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -725,13 +725,13 @@ const CGFloat kTypingCellHeight = 24; { id notificationObject = notification.object; - if ([notificationObject isKindOfClass:MXKeyVerificationByDMRequest.class]) + if ([notificationObject conformsToProtocol:@protocol(MXKeyVerificationRequest)]) { - MXKeyVerificationByDMRequest *keyVerificationByDMRequest = (MXKeyVerificationByDMRequest*)notificationObject; + id keyVerificationRequest = (id)notificationObject; - if ([keyVerificationByDMRequest.roomId isEqualToString:self.roomId]) + if (keyVerificationRequest.transport == MXKeyVerificationTransportDirectMessage && [keyVerificationRequest.roomId isEqualToString:self.roomId]) { - RoomBubbleCellData *roomBubbleCellData = [self roomBubbleCellDataForEventId:keyVerificationByDMRequest.eventId]; + RoomBubbleCellData *roomBubbleCellData = [self roomBubbleCellDataForEventId:keyVerificationRequest.requestId]; roomBubbleCellData.isKeyVerificationOperationPending = NO; roomBubbleCellData.keyVerification = nil; @@ -866,6 +866,7 @@ const CGFloat kTypingCellHeight = 24; } __block MXHTTPOperation *operation = [self.mxSession.crypto.keyVerificationManager keyVerificationFromKeyVerificationEvent:event + roomId:self.roomId success:^(MXKeyVerification * _Nonnull keyVerification) { BOOL shouldRefreshCells = bubbleCellData.isKeyVerificationOperationPending || bubbleCellData.keyVerification == nil; diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.swift b/Riot/Modules/Room/DataSources/RoomDataSource.swift index f57896b62..281a7a046 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.swift +++ b/Riot/Modules/Room/DataSources/RoomDataSource.swift @@ -21,7 +21,7 @@ extension RoomDataSource { private enum Constants { static let emoteMessageSlashCommandPrefix = String(format: "%@ ", kMXKSlashCmdEmote) } - + // MARK: - NSAttributedString Sending /// Send a text message to the room. /// While sending, a fake event will be echoed in the messages list. @@ -33,7 +33,7 @@ extension RoomDataSource { func sendAttributedTextMessage(_ attributedText: NSAttributedString, completion: @escaping (MXResponse) -> Void) { var localEcho: MXEvent? - + let isEmote = isAttributedTextMessageAnEmote(attributedText) let sanitized = sanitizedAttributedMessageText(attributedText) let rawText: String @@ -43,7 +43,7 @@ extension RoomDataSource { } else { rawText = sanitized.string } - + if isEmote { room.sendEmote(rawText, formattedText: html, @@ -57,13 +57,38 @@ extension RoomDataSource { localEcho: &localEcho, completion: completion) } - + if localEcho != nil { self.queueEvent(forProcessing: localEcho, with: self.roomState, direction: .forwards) self.processQueuedEvents(nil) } } - + + // MARK: - NSAttributedString Sending + /// Send a text message to the room. + /// While sending, a fake event will be echoed in the messages list. + /// Once complete, this local echo will be replaced by the event saved by the homeserver. + /// + /// - Parameters: + /// - rawText: the raw text to send + /// - html: the formatted html to send + /// - completion: http operation completion block + func sendFormattedTextMessage(_ rawText: String, + html: String, + completion: @escaping (MXResponse) -> Void) { + var localEcho: MXEvent? + room.sendTextMessage(rawText, + formattedText: html, + threadId: self.threadId, + localEcho: &localEcho, + completion: completion) + + if localEcho != nil { + self.queueEvent(forProcessing: localEcho, with: self.roomState, direction: .forwards) + self.processQueuedEvents(nil) + } + } + /// Send a reply to an event with text message to the room. /// /// While sending, a fake event will be echoed in the messages list. @@ -76,8 +101,6 @@ extension RoomDataSource { func sendReply(to eventToReply: MXEvent, withAttributedTextMessage attributedText: NSAttributedString, completion: @escaping (MXResponse) -> Void) { - var localEcho: MXEvent? - let sanitized = sanitizedAttributedMessageText(attributedText) let rawText: String let html: String? = htmlMessageFromSanitizedAttributedText(sanitized) @@ -86,23 +109,29 @@ extension RoomDataSource { } else { rawText = sanitized.string } - - let stringLocalizer: MXSendReplyEventStringLocalizerProtocol = MXKSendReplyEventStringLocalizer() - - room.sendReply(to: eventToReply, - textMessage: rawText, - formattedTextMessage: html, - stringLocalizer: stringLocalizer, - threadId: self.threadId, - localEcho: &localEcho, - completion: completion) - - if localEcho != nil { - self.queueEvent(forProcessing: localEcho, with: self.roomState, direction: .forwards) - self.processQueuedEvents(nil) - } + + handleFormattedSendReply(to: eventToReply, rawText: rawText, html: html, completion: completion) } - + + /// Send a reply to an event with a html formatted text message to the room. + /// + /// While sending, a fake event will be echoed in the messages list. + /// Once complete, this local echo will be replaced by the event saved by the homeserver. + /// + /// - Parameters: + /// - eventToReply: the event to reply + /// - rawText: the raw text to send + /// - htmlText: the html text to send + /// - completion: http operation completion block + func sendReply(to eventToReply: MXEvent, + rawText: String, + htmlText: String, + completion: @escaping (MXResponse) -> Void) { + + handleFormattedSendReply(to: eventToReply, rawText: rawText, html: htmlText, completion: completion) + } + + /// Replace a text in an event. /// /// - Parameters: @@ -122,29 +151,24 @@ extension RoomDataSource { } else { rawText = sanitized.string } - - let eventBody = event.content[kMXMessageBodyKey] as? String - let eventFormattedBody = event.content["formatted_body"] as? String - - if rawText != eventBody && (eventFormattedBody == nil || html != eventFormattedBody) { - self.mxSession.aggregations.replaceTextMessageEvent( - event, - withTextMessage: rawText, - formattedText: html, - localEcho: { localEcho in - // Apply the local echo to the timeline - self.updateEvent(withReplace: localEcho) - - // Integrate the replace local event into the timeline like when sending a message - // This also allows to manage read receipt on this replace event - self.queueEvent(forProcessing: localEcho, with: self.roomState, direction: .forwards) - self.processQueuedEvents(nil) - }, - success: success, - failure: failure) - } else { - failure(nil) - } + + handleReplaceFormattedMessage(for: event, rawText: rawText, html: html, success: success, failure: failure) + } + + /// Replace a formatted html text in an event + /// + /// - Parameters: + /// - event: The event to replace + /// - rawText: The new rawText + /// - html: The new html text + /// - success: A block object called when the operation succeeds. It returns the event id of the event generated on the homeserver + /// - failure: A block object called when the operation fails + func replaceFormattedTextMessage( for event: MXEvent, + rawText: String, + html: String, + success: @escaping ((String?) -> Void), + failure: @escaping ((Error?) -> Void)) { + handleReplaceFormattedMessage(for: event, rawText: rawText, html: html, success: success, failure: failure) } /// Retrieve editable attributed text message from an event. @@ -197,8 +221,10 @@ extension RoomDataSource { return editableTextMessage } - + @objc func editableHtmlTextMessage(for event: MXEvent) -> String { + event.content["formatted_body"] as? String ?? event.content["body"] as? String ?? "" + } } // MARK: - Private Helpers @@ -232,4 +258,54 @@ private extension RoomDataSource { func isAttributedTextMessageAnEmote(_ attributedText: NSAttributedString) -> Bool { return attributedText.string.starts(with: Constants.emoteMessageSlashCommandPrefix) } + + func handleReplaceFormattedMessage(for event: MXEvent, + rawText: String, + html: String?, + success: @escaping ((String?) -> Void), + failure: @escaping ((Error?) -> Void)) { + let eventBody = event.content[kMXMessageBodyKey] as? String + let eventFormattedBody = event.content["formatted_body"] as? String + if rawText != eventBody && (eventFormattedBody == nil || html != eventFormattedBody) { + self.mxSession.aggregations.replaceTextMessageEvent( + event, + withTextMessage: rawText, + formattedText: html, + localEcho: { localEcho in + // Apply the local echo to the timeline + self.updateEvent(withReplace: localEcho) + + // Integrate the replace local event into the timeline like when sending a message + // This also allows to manage read receipt on this replace event + self.queueEvent(forProcessing: localEcho, with: self.roomState, direction: .forwards) + self.processQueuedEvents(nil) + }, + success: success, + failure: failure) + } else { + failure(nil) + } + } + + func handleFormattedSendReply(to eventToReply: MXEvent, + rawText: String, + html: String?, + completion: @escaping (MXResponse) -> Void) { + var localEcho: MXEvent? + + let stringLocalizer: MXSendReplyEventStringLocalizerProtocol = MXKSendReplyEventStringLocalizer() + + room.sendReply(to: eventToReply, + textMessage: rawText, + formattedTextMessage: html, + stringLocalizer: stringLocalizer, + threadId: self.threadId, + localEcho: &localEcho, + completion: completion) + + if localEcho != nil { + self.queueEvent(forProcessing: localEcho, with: self.roomState, direction: .forwards) + self.processQueuedEvents(nil) + } + } } diff --git a/Riot/Modules/Room/MXKRoomViewController.m b/Riot/Modules/Room/MXKRoomViewController.m index 9eb3ad3b2..b0f547bc4 100644 --- a/Riot/Modules/Room/MXKRoomViewController.m +++ b/Riot/Modules/Room/MXKRoomViewController.m @@ -1116,7 +1116,7 @@ MXLogDebug(@"[MXKRoomVC] setRoomInputToolbarViewClass: Set inputToolbarView to class %@", roomInputToolbarViewClass); - id inputToolbarView = [roomInputToolbarViewClass roomInputToolbarView]; + id inputToolbarView = [roomInputToolbarViewClass instantiateRoomInputToolbarView]; self->inputToolbarView = inputToolbarView; self->inputToolbarView.delegate = self; @@ -3359,32 +3359,34 @@ - (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView heightDidChanged:(CGFloat)height completion:(void (^)(BOOL finished))completion { - _roomInputToolbarContainerHeightConstraint.constant = height; - - // Update layout with animation - [UIView animateWithDuration:self.resizeComposerAnimationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn - animations:^{ - // We will scroll to bottom if the bottom of the table is currently visible - BOOL shouldScrollToBottom = [self isBubblesTableScrollViewAtTheBottom]; - - CGFloat bubblesTableViewBottomConst = self->_roomInputToolbarContainerBottomConstraint.constant + self->_roomInputToolbarContainerHeightConstraint.constant + self->_roomActivitiesContainerHeightConstraint.constant; - - self->_bubblesTableViewBottomConstraint.constant = bubblesTableViewBottomConst; - - // Force to render the view - [self.view layoutIfNeeded]; - - if (shouldScrollToBottom) - { - [self scrollBubblesTableViewToBottomAnimated:NO]; - } - } - completion:^(BOOL finished){ - if (completion) - { - completion(finished); - } - }]; + // This dispatch fixes a simultaneous accesses crash if this gets called twice quickly in succession + dispatch_async(dispatch_get_main_queue(), ^{ + // Update layout with animation + [UIView animateWithDuration:self.resizeComposerAnimationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn + animations:^{ + // We will scroll to bottom if the bottom of the table is currently visible + BOOL shouldScrollToBottom = [self isBubblesTableScrollViewAtTheBottom]; + + self->_roomInputToolbarContainerHeightConstraint.constant = height; + CGFloat bubblesTableViewBottomConst = self->_roomInputToolbarContainerBottomConstraint.constant + self->_roomInputToolbarContainerHeightConstraint.constant + self->_roomActivitiesContainerHeightConstraint.constant; + + self->_bubblesTableViewBottomConstraint.constant = bubblesTableViewBottomConst; + + // Force to render the view + [self.view layoutIfNeeded]; + + if (shouldScrollToBottom) + { + [self scrollBubblesTableViewToBottomAnimated:NO]; + } + } + completion:^(BOOL finished){ + if (completion) + { + completion(finished); + } + }]; + }); } - (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView sendTextMessage:(NSString*)textMessage diff --git a/Riot/Modules/Room/MXKRoomViewController.xib b/Riot/Modules/Room/MXKRoomViewController.xib index 1ad5d0650..c1a016ff5 100644 --- a/Riot/Modules/Room/MXKRoomViewController.xib +++ b/Riot/Modules/Room/MXKRoomViewController.xib @@ -1,10 +1,9 @@ - - - - + + - + + @@ -59,6 +58,7 @@ + diff --git a/Riot/Modules/Room/RoomCoordinator.swift b/Riot/Modules/Room/RoomCoordinator.swift index 1eeca7f0c..3ba9d8793 100644 --- a/Riot/Modules/Room/RoomCoordinator.swift +++ b/Riot/Modules/Room/RoomCoordinator.swift @@ -92,6 +92,7 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol { self.roomViewController.parentSpaceId = parameters.parentSpaceId TimelinePollProvider.shared.session = parameters.session + TimelineVoiceBroadcastProvider.shared.session = parameters.session super.init() } diff --git a/Riot/Modules/Room/RoomViewController.h b/Riot/Modules/Room/RoomViewController.h index a01f6c567..7da32359f 100644 --- a/Riot/Modules/Room/RoomViewController.h +++ b/Riot/Modules/Room/RoomViewController.h @@ -33,6 +33,7 @@ @class RoomDisplayConfiguration; @class ThreadsCoordinatorBridgePresenter; @class LiveLocationSharingBannerView; +@class VoiceBroadcastService; NS_ASSUME_NONNULL_BEGIN @@ -107,6 +108,9 @@ extern NSNotificationName const RoomGroupCallTileTappedNotification; // The customized room data source for Vector @property (nonatomic, nullable) RoomDataSource *customizedRoomDataSource; +// The voice broadcast service +@property (nonatomic, nullable) VoiceBroadcastService *voiceBroadcastService; + /** Retrieve the live data source in cases where the timeline is not live. diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 542007a41..a1c520302 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -97,7 +97,7 @@ static CGSize kThreadListBarButtonItemImageSize; @interface RoomViewController () + RoomDataSourceDelegate, RoomCreationModalCoordinatorBridgePresenterDelegate, RoomInfoCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, RemoveJitsiWidgetViewDelegate, VoiceMessageControllerDelegate, SpaceDetailPresenterDelegate, UserSuggestionCoordinatorBridgeDelegate, ThreadsCoordinatorBridgePresenterDelegate, ThreadsBetaCoordinatorBridgePresenterDelegate, MXThreadingServiceDelegate, RoomParticipantsInviteCoordinatorBridgePresenterDelegate, RoomInputToolbarViewDelegate, ComposerCreateActionListBridgePresenterDelegate> { // The preview header @@ -195,6 +195,7 @@ static CGSize kThreadListBarButtonItemImageSize; @property (nonatomic, strong) RoomContextualMenuPresenter *roomContextualMenuPresenter; @property (nonatomic, strong) MXKErrorAlertPresentation *errorPresenter; @property (nonatomic, strong) NSAttributedString *textMessageBeforeEditing; +@property (nonatomic, strong) NSString *htmlTextBeforeEditing; @property (nonatomic, strong) EditHistoryCoordinatorBridgePresenter *editHistoryPresenter; @property (nonatomic, strong) MXKDocumentPickerPresenter *documentPickerPresenter; @property (nonatomic, strong) EmojiPickerCoordinatorBridgePresenter *emojiPickerCoordinatorBridgePresenter; @@ -209,6 +210,7 @@ static CGSize kThreadListBarButtonItemImageSize; @property (nonatomic, strong) ThreadsCoordinatorBridgePresenter *threadsBridgePresenter; @property (nonatomic, strong) ThreadsBetaCoordinatorBridgePresenter *threadsBetaBridgePresenter; @property (nonatomic, strong) SlidingModalPresenter *threadsNoticeModalPresenter; +@property (nonatomic, strong) ComposerCreateActionListBridgePresenter *composerCreateActionListBridgePresenter; @property (nonatomic, getter=isActivitiesViewExpanded) BOOL activitiesViewExpanded; @property (nonatomic, getter=isScrollToBottomHidden) BOOL scrollToBottomHidden; @property (nonatomic, getter=isMissedDiscussionsBadgeHidden) BOOL missedDiscussionsBadgeHidden; @@ -672,9 +674,7 @@ static CGSize kThreadListBarButtonItemImageSize; { // Retrieve the potential message partially typed during last room display. // Note: We have to wait for viewDidAppear before updating growingTextView (viewWillAppear is too early) - RoomInputToolbarView *inputToolbar = (RoomInputToolbarView *)self.inputToolbarView; - - inputToolbar.attributedTextMessage = self.roomDataSource.partialAttributedTextMessage; + self.inputToolbarView.attributedTextMessage = self.roomDataSource.partialAttributedTextMessage; } } @@ -1152,10 +1152,23 @@ static CGSize kThreadListBarButtonItemImageSize; [self notifyDelegateOnLeaveRoomIfNecessary]; } + ++ (Class) mainToolbarClass +{ + if (RiotSettings.shared.enableWysiwygComposer) + { + return WysiwygInputToolbarView.class; + } + else + { + return RoomInputToolbarView.class; + } +} + // Set the input toolbar according to the current display - (void)updateRoomInputToolbarViewClassIfNeeded { - Class roomInputToolbarViewClass = RoomInputToolbarView.class; + Class roomInputToolbarViewClass = [RoomViewController mainToolbarClass]; BOOL shouldDismissContextualMenu = NO; @@ -1198,10 +1211,10 @@ static CGSize kThreadListBarButtonItemImageSize; { [super setRoomInputToolbarViewClass:roomInputToolbarViewClass]; - // The voice message toolbar cannot be set on DisabledInputToolbarView and on new direct chat. - if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class] && !self.isNewDirectChat) - { - [(RoomInputToolbarView *)self.inputToolbarView setVoiceMessageToolbarView:self.voiceMessageController.voiceMessageToolbarView]; + + if ([self.inputToolbarView.class conformsToProtocol:@protocol(RoomInputToolbarViewProtocol)]) { + id inputToolbar = (id)self.inputToolbarView; + [inputToolbar setVoiceMessageToolbarView:self.voiceMessageController.voiceMessageToolbarView]; } [self updateInputToolBarViewHeight]; @@ -1214,9 +1227,9 @@ static CGSize kThreadListBarButtonItemImageSize; { CGFloat height = 0; - if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) - { - height = ((RoomInputToolbarView*)self.inputToolbarView).mainToolbarHeightConstraint.constant; + if ([self.inputToolbarView.class conformsToProtocol:@protocol(RoomInputToolbarViewProtocol)]) { + id inputToolbar = (id)self.inputToolbarView; + height = inputToolbar.toolbarHeight; } else if ([self.inputToolbarView isKindOfClass:DisabledRoomInputToolbarView.class]) { @@ -2029,9 +2042,9 @@ static CGSize kThreadListBarButtonItemImageSize; - (void)setInputToolBarSendMode:(RoomInputToolbarViewSendMode)sendMode forEventWithId:(NSString *)eventId { - if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:[RoomInputToolbarView class]]) + if (self.inputToolbarView && [self inputToolbarConformsToToolbarViewProtocol]) { - RoomInputToolbarView *roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; + MXKRoomInputToolbarView *roomInputToolbarView = (MXKRoomInputToolbarView *) self.inputToolbarView; if (eventId) { MXEvent *event = [self.roomDataSource eventWithEventId:eventId]; @@ -2165,11 +2178,9 @@ static CGSize kThreadListBarButtonItemImageSize; UIView *sourceView; - RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; - - if (roomInputToolbarView) + if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { - sourceView = roomInputToolbarView.attachMediaButton; + sourceView = ((RoomInputToolbarView*)self.inputToolbarView).attachMediaButton; } else { @@ -2241,6 +2252,7 @@ static CGSize kThreadListBarButtonItemImageSize; } - (void)setupActions { + if (![self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { return; } @@ -2308,6 +2320,35 @@ static CGSize kThreadListBarButtonItemImageSize; [self showCameraControllerAnimated:YES]; }]]; } + if (RiotSettings.shared.enableVoiceBroadcast && !self.isNewDirectChat) + { + [actionItems addObject:[[RoomActionItem alloc] initWithImage:AssetImages.actionLive.image andAction:^{ + MXStrongifyAndReturnIfNil(self); + if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) { + ((RoomInputToolbarView *) self.inputToolbarView).actionMenuOpened = NO; + } + + // TODO: Init and start voice broadcast + MXSession* session = self.roomDataSource.mxSession; + [session getOrCreateVoiceBroadcastServiceFor:self.roomDataSource.room completion:^(VoiceBroadcastService *voiceBroadcastService) { + if (voiceBroadcastService) { + if ([VoiceBroadcastInfo isStoppedFor:[voiceBroadcastService getState]]) { + [session.voiceBroadcastService startVoiceBroadcastWithSuccess:^(NSString * _Nullable success) { + + } failure:^(NSError * _Nonnull error) { + + }]; + } else { + [session.voiceBroadcastService stopVoiceBroadcastWithSuccess:^(NSString * _Nullable success) { + + } failure:^(NSError * _Nonnull error) { + + }]; + } + } + }]; + }]]; + } roomInputView.actionsBar.actionItems = actionItems; } @@ -2403,8 +2444,7 @@ static CGSize kThreadListBarButtonItemImageSize; */ - (void)sendVideoAsset:(AVAsset *)videoAsset isPhotoLibraryAsset:(BOOL)isPhotoLibraryAsset { - RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; - if (!roomInputToolbarView) + if (![self inputToolbarConformsToToolbarViewProtocol]) { return; } @@ -2425,15 +2465,27 @@ static CGSize kThreadListBarButtonItemImageSize; // Create before sending the message in case of a discussion (direct chat) [self createDiscussionIfNeeded:^(BOOL readyToSend) { - if (readyToSend) + if (readyToSend && [self inputToolbarConformsToToolbarViewProtocol]) { - [[self inputToolbarViewAsRoomInputToolbarView] sendSelectedVideoAsset:videoAsset isPhotoLibraryAsset:isPhotoLibraryAsset]; + [self.inputToolbarView sendSelectedVideoAsset:videoAsset isPhotoLibraryAsset:isPhotoLibraryAsset]; } // Errors are handled at the request level. This should be improved in case of code rewriting. }]; }]; - compressionPrompt.popoverPresentationController.sourceView = roomInputToolbarView.attachMediaButton; - compressionPrompt.popoverPresentationController.sourceRect = roomInputToolbarView.attachMediaButton.bounds; + + UIView *sourceView; + + if ([self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) + { + sourceView = ((RoomInputToolbarView*)self.inputToolbarView).attachMediaButton; + } + else + { + sourceView = self.inputToolbarView; + } + + compressionPrompt.popoverPresentationController.sourceView = sourceView; + compressionPrompt.popoverPresentationController.sourceRect = sourceView.bounds; [self presentViewController:compressionPrompt animated:YES completion:nil]; } @@ -2444,9 +2496,9 @@ static CGSize kThreadListBarButtonItemImageSize; // Create before sending the message in case of a discussion (direct chat) [self createDiscussionIfNeeded:^(BOOL readyToSend) { - if (readyToSend) + if (readyToSend && [self inputToolbarConformsToToolbarViewProtocol]) { - [[self inputToolbarViewAsRoomInputToolbarView] sendSelectedVideoAsset:videoAsset isPhotoLibraryAsset:isPhotoLibraryAsset]; + [self.inputToolbarView sendSelectedVideoAsset:videoAsset isPhotoLibraryAsset:isPhotoLibraryAsset]; } // Errors are handled at the request level. This should be improved in case of code rewriting. }]; @@ -3159,6 +3211,39 @@ static CGSize kThreadListBarButtonItemImageSize; } } } + else if (bubbleData.tag == RoomBubbleCellDataTagVoiceBroadcast) + { + if (bubbleData.isIncoming) + { + if (bubbleData.isPaginationFirstBubble) + { + cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceBroadcastWithPaginationTitle; + } + else if (bubbleData.shouldHideSenderInformation) + { + cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceBroadcastWithoutSenderInfo; + } + else + { + cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceBroadcast; + } + } + else + { + if (bubbleData.isPaginationFirstBubble) + { + cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithPaginationTitle; + } + else if (bubbleData.shouldHideSenderInformation) + { + cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithoutSenderInfo; + } + else + { + cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcast; + } + } + } else if (roomBubbleCellData.getFirstBubbleComponentWithDisplay.event.isEmote) { if (bubbleData.isIncoming) @@ -4579,12 +4664,16 @@ static CGSize kThreadListBarButtonItemImageSize; { MXEvent *event = [self.roomDataSource eventWithEventId:eventId]; - RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; - - if (roomInputToolbarView) + if ([self inputToolbarConformsToHtmlToolbarViewProtocol]) { - self.textMessageBeforeEditing = roomInputToolbarView.attributedTextMessage; - roomInputToolbarView.attributedTextMessage = [self.customizedRoomDataSource editableAttributedTextMessageFor:event]; + MXKRoomInputToolbarView *htmlInputToolBarView = (MXKRoomInputToolbarView *) self.inputToolbarView; + self.htmlTextBeforeEditing = htmlInputToolBarView.htmlContent; + htmlInputToolBarView.htmlContent = [self.customizedRoomDataSource editableHtmlTextMessageFor:event]; + } + else if ([self inputToolbarConformsToToolbarViewProtocol]) + { + self.textMessageBeforeEditing = self.inputToolbarView.attributedTextMessage; + self.inputToolbarView.attributedTextMessage = [self.customizedRoomDataSource editableAttributedTextMessageFor:event]; } [self selectEventWithId:eventId inputToolBarSendMode:RoomInputToolbarViewSendModeEdit showTimestamp:YES]; @@ -4592,26 +4681,30 @@ static CGSize kThreadListBarButtonItemImageSize; - (void)restoreTextMessageBeforeEditing { - RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; - if (self.textMessageBeforeEditing) + + if (self.htmlTextBeforeEditing && [self inputToolbarConformsToHtmlToolbarViewProtocol]) { - roomInputToolbarView.attributedTextMessage = self.textMessageBeforeEditing; + MXKRoomInputToolbarView *htmlInputToolBarView = (MXKRoomInputToolbarView *) self.inputToolbarView; + htmlInputToolBarView.htmlContent = self.htmlTextBeforeEditing; + } + else if (self.textMessageBeforeEditing && [self inputToolbarConformsToToolbarViewProtocol]) + { + self.inputToolbarView.attributedTextMessage = self.textMessageBeforeEditing; } self.textMessageBeforeEditing = nil; + self.htmlTextBeforeEditing = nil; } -- (RoomInputToolbarView*)inputToolbarViewAsRoomInputToolbarView +- (BOOL)inputToolbarConformsToHtmlToolbarViewProtocol { - RoomInputToolbarView *roomInputToolbarView; - - if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:[RoomInputToolbarView class]]) - { - roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; - } - - return roomInputToolbarView; + return [self.inputToolbarView conformsToProtocol:@protocol(HtmlRoomInputToolbarViewProtocol)]; +} + +- (BOOL)inputToolbarConformsToToolbarViewProtocol +{ + return [self.inputToolbarView conformsToProtocol:@protocol(RoomInputToolbarViewProtocol)]; } - (void)showDifferentURLsAlertFor:(NSURL *)url visibleURLString:(NSString *)visibleURLString @@ -4904,7 +4997,7 @@ static CGSize kThreadListBarButtonItemImageSize; { if (self.roomInputToolbarContainerHeightConstraint.constant != height) { - // Hide temporarily the placeholder to prevent its distorsion during height animation + // Hide temporarily the placeholder to prevent its distortion during height animation if (!savedInputToolbarPlaceholder) { savedInputToolbarPlaceholder = toolbarView.placeholder.length ? toolbarView.placeholder : @""; @@ -4929,7 +5022,7 @@ static CGSize kThreadListBarButtonItemImageSize; } } -- (void)roomInputToolbarViewDidTapCancel:(RoomInputToolbarView*)toolbarView +- (void)roomInputToolbarViewDidTapCancel:(MXKRoomInputToolbarView*)toolbarView { [self cancelEventSelection]; } @@ -4948,6 +5041,53 @@ static CGSize kThreadListBarButtonItemImageSize; } } +- (void)roomInputToolbarView:(RoomInputToolbarView *)toolbarView sendFormattedTextMessage:(NSString *)formattedTextMessage withRawText:(NSString *)rawText +{ + // Create before sending the message in case of a discussion (direct chat) + MXWeakify(self); + [self createDiscussionIfNeeded:^(BOOL readyToSend) { + MXStrongifyAndReturnIfNil(self); + + if (readyToSend) { + [self sendFormattedTextMessage:rawText htmlMsg:formattedTextMessage]; + } + // Errors are handled at the request level. This should be improved in case of code rewriting. + }]; +} + +- (void)roomInputToolbarViewShowSendMediaActions:(MXKRoomInputToolbarView *)toolbarView +{ + NSMutableArray *actionItems = [NSMutableArray new]; + if (RiotSettings.shared.roomScreenAllowMediaLibraryAction) + { + [actionItems addObject:@(ComposerCreateActionPhotoLibrary)]; + } + if (RiotSettings.shared.roomScreenAllowStickerAction && !self.isNewDirectChat) + { + [actionItems addObject:@(ComposerCreateActionStickers)]; + } + if (RiotSettings.shared.roomScreenAllowFilesAction) + { + [actionItems addObject:@(ComposerCreateActionAttachments)]; + } + if (BuildSettings.pollsEnabled && self.displayConfiguration.sendingPollsEnabled && !self.isNewDirectChat) + { + [actionItems addObject:@(ComposerCreateActionPolls)]; + } + if (BuildSettings.locationSharingEnabled && !self.isNewDirectChat) + { + [actionItems addObject:@(ComposerCreateActionLocation)]; + } + if (RiotSettings.shared.roomScreenAllowCameraAction) + { + [actionItems addObject:@(ComposerCreateActionCamera)]; + } + + self.composerCreateActionListBridgePresenter = [[ComposerCreateActionListBridgePresenter alloc] initWithActions:actionItems]; + self.composerCreateActionListBridgePresenter.delegate = self; + [self.composerCreateActionListBridgePresenter presentFrom:self animated:YES]; +} + - (void)roomInputToolbarView:(RoomInputToolbarView *)toolbarView sendAttributedTextMessage:(NSAttributedString *)attributedTextMessage { // Create before sending the message in case of a discussion (direct chat) @@ -5294,7 +5434,7 @@ static CGSize kThreadListBarButtonItemImageSize; else { // Enable back the text input - [self setRoomInputToolbarViewClass:RoomInputToolbarView.class]; + [self setRoomInputToolbarViewClass:[RoomViewController mainToolbarClass]]; [self updateInputToolBarViewHeight]; // And the extra area @@ -7546,9 +7686,9 @@ static CGSize kThreadListBarButtonItemImageSize; // Create before sending the message in case of a discussion (direct chat) [self createDiscussionIfNeeded:^(BOOL readyToSend) { - if (readyToSend) + if (readyToSend && [self inputToolbarConformsToToolbarViewProtocol]) { - [[self inputToolbarViewAsRoomInputToolbarView] sendSelectedImage:imageData + [self.inputToolbarView sendSelectedImage:imageData withMimeType:MXKUTI.jpeg.mimeType andCompressionMode:MediaCompressionHelper.defaultCompressionMode isPhotoLibraryAsset:NO]; @@ -7581,9 +7721,9 @@ static CGSize kThreadListBarButtonItemImageSize; // Create before sending the message in case of a discussion (direct chat) [self createDiscussionIfNeeded:^(BOOL readyToSend) { - if (readyToSend) + if (readyToSend && [self inputToolbarConformsToToolbarViewProtocol]) { - [[self inputToolbarViewAsRoomInputToolbarView] sendSelectedImage:imageData + [self.inputToolbarView sendSelectedImage:imageData withMimeType:uti.mimeType andCompressionMode:MediaCompressionHelper.defaultCompressionMode isPhotoLibraryAsset:YES]; @@ -7610,9 +7750,9 @@ static CGSize kThreadListBarButtonItemImageSize; // Create before sending the message in case of a discussion (direct chat) [self createDiscussionIfNeeded:^(BOOL readyToSend) { - if (readyToSend) + if (readyToSend && [self inputToolbarConformsToToolbarViewProtocol]) { - [[self inputToolbarViewAsRoomInputToolbarView] sendSelectedAssets:assets withCompressionMode:MediaCompressionHelper.defaultCompressionMode]; + [self.inputToolbarView sendSelectedAssets:assets withCompressionMode:MediaCompressionHelper.defaultCompressionMode]; } // Errors are handled at the request level. This should be improved in case of code rewriting. }]; @@ -7851,4 +7991,39 @@ static CGSize kThreadListBarButtonItemImageSize; } } +#pragma mark - ComposerCreateActionListBridgePresenter + +- (void)composerCreateActionListBridgePresenterDelegateDidComplete:(ComposerCreateActionListBridgePresenter *)coordinatorBridgePresenter action:(enum ComposerCreateAction)action +{ + + [coordinatorBridgePresenter dismissWithAnimated:true completion:^{ + switch (action) { + case ComposerCreateActionPhotoLibrary: + [self showMediaPickerAnimated:YES]; + break; + case ComposerCreateActionStickers: + [self roomInputToolbarViewPresentStickerPicker]; + break; + case ComposerCreateActionAttachments: + [self roomInputToolbarViewDidTapFileUpload]; + break; + case ComposerCreateActionPolls: + [self.delegate roomViewControllerDidRequestPollCreationFormPresentation:self]; + break; + case ComposerCreateActionLocation: + [self.delegate roomViewControllerDidRequestLocationSharingFormPresentation:self]; + break; + case ComposerCreateActionCamera: + [self showCameraControllerAnimated:YES]; + break; + } + self.composerCreateActionListBridgePresenter = nil; + }]; +} + +- (void)composerCreateActionListBridgePresenterDidDismissInteractively:(ComposerCreateActionListBridgePresenter *)coordinatorBridgePresenter +{ + self.composerCreateActionListBridgePresenter = nil; +} + @end diff --git a/Riot/Modules/Room/RoomViewController.swift b/Riot/Modules/Room/RoomViewController.swift index 3fc72f6ab..7bbc6812c 100644 --- a/Riot/Modules/Room/RoomViewController.swift +++ b/Riot/Modules/Room/RoomViewController.swift @@ -52,6 +52,55 @@ extension RoomViewController { } + /// Send the formatted text message and its raw counterpat to the room + /// + /// - Parameter rawTextMsg: the raw text message + /// - Parameter htmlMsg: the html text message + @objc func sendFormattedTextMessage(_ rawTextMsg: String, htmlMsg: String) { + let eventModified = self.roomDataSource.event(withEventId: customizedRoomDataSource?.selectedEventId) + self.setupRoomDataSource { roomDataSource in + guard let roomDataSource = roomDataSource as? RoomDataSource else { return } + if self.wysiwygInputToolbar?.sendMode == .reply, let eventModified = eventModified { + roomDataSource.sendReply(to: eventModified, rawText: rawTextMsg, htmlText: htmlMsg) { response in + switch response { + case .success: + break + case .failure: + MXLog.error("[RoomViewController] sendFormattedTextMessage failed while updating event", context: [ + "event_id": eventModified.eventId + ]) + } + } + } else if self.wysiwygInputToolbar?.sendMode == .edit, let eventModified = eventModified { + roomDataSource.replaceFormattedTextMessage( + for: eventModified, + rawText: rawTextMsg, + html: htmlMsg, + success: { _ in + // + }, + failure: { _ in + MXLog.error("[RoomViewController] sendFormattedTextMessage failed while updating event", context: [ + "event_id": eventModified.eventId + ]) + }) + } else { + roomDataSource.sendFormattedTextMessage(rawTextMsg, html: htmlMsg) { response in + switch response { + case .success: + break + case .failure: + MXLog.error("[RoomViewController] sendFormattedTextMessage failed") + } + } + } + + if self.customizedRoomDataSource?.selectedEventId != nil { + self.cancelEventSelection() + } + } + } + /// Send given attributed text message to the room /// /// - Parameter attributedTextMsg: the attributed text message @@ -107,4 +156,8 @@ private extension RoomViewController { var inputToolbar: RoomInputToolbarView? { return self.inputToolbarView as? RoomInputToolbarView } + + var wysiwygInputToolbar: WysiwygInputToolbarView? { + return self.inputToolbarView as? WysiwygInputToolbarView + } } diff --git a/Riot/Modules/Room/TimelineCells/RoomTimelineCellIdentifier.h b/Riot/Modules/Room/TimelineCells/RoomTimelineCellIdentifier.h index fafce9df7..640a2e3bc 100644 --- a/Riot/Modules/Room/TimelineCells/RoomTimelineCellIdentifier.h +++ b/Riot/Modules/Room/TimelineCells/RoomTimelineCellIdentifier.h @@ -168,6 +168,16 @@ typedef NS_ENUM(NSUInteger, RoomTimelineCellIdentifier) { RoomTimelineCellIdentifierOutgoingLocationWithoutSenderInfo, RoomTimelineCellIdentifierOutgoingLocationWithPaginationTitle, + // - Voice broadcast + // -- Incoming + RoomTimelineCellIdentifierIncomingVoiceBroadcast, + RoomTimelineCellIdentifierIncomingVoiceBroadcastWithoutSenderInfo, + RoomTimelineCellIdentifierIncomingVoiceBroadcastWithPaginationTitle, + // -- Outgoing + RoomTimelineCellIdentifierOutgoingVoiceBroadcast, + RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithoutSenderInfo, + RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithPaginationTitle, + // - Others RoomTimelineCellIdentifierEmpty, RoomTimelineCellIdentifierSelectedSticker, diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m b/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m index 2a9626796..42bad501d 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m @@ -132,6 +132,17 @@ [tableView registerClass:FileWithoutThumbnailOutoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:FileWithoutThumbnailOutoingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; } +- (void)registerVoiceBroadcastCellsForTableView:(UITableView*)tableView +{ + // Incoming + [tableView registerClass:VoiceBroadcastIncomingBubbleCell.class forCellReuseIdentifier:VoiceBroadcastIncomingBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:VoiceBroadcastIncomingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceBroadcastIncomingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + // Outgoing + [tableView registerClass:VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.defaultReuseIdentifier]; + [tableView registerClass:VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.defaultReuseIdentifier]; +} + #pragma mark - Mapping - (NSDictionary*)incomingTextMessageCellsMapping @@ -293,4 +304,18 @@ }; } +- (NSDictionary*)voiceBroadcastCellsMapping +{ + return @{ + // Incoming + @(RoomTimelineCellIdentifierIncomingVoiceBroadcast) : VoiceBroadcastIncomingBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingVoiceBroadcastWithoutSenderInfo) : VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierIncomingVoiceBroadcastWithPaginationTitle) : VoiceBroadcastIncomingWithPaginationTitleBubbleCell.class, + // Outgoing + @(RoomTimelineCellIdentifierOutgoingVoiceBroadcast) : VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithoutSenderInfo) : VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.class, + @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithPaginationTitle) : VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.class, + }; +} + @end diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingBubbleCell.swift new file mode 100644 index 000000000..f46acbae1 --- /dev/null +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingBubbleCell.swift @@ -0,0 +1,39 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation + +class VoiceBroadcastIncomingBubbleCell: VoiceBroadcastBubbleCell, BubbleIncomingRoomCellProtocol { + + override func setupViews() { + super.setupViews() + + // TODO: VB update margins attributes + let leftMargin: CGFloat = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.left + BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.left + let rightMargin: CGFloat = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.right + BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.right + + roomCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin + roomCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin + + self.setupBubbleDecorations() + } + + override func update(theme: Theme) { + super.update(theme: theme) + + self.bubbleBackgroundColor = theme.roomCellIncomingBubbleBackgroundColor + } +} diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..6bbb10d9a --- /dev/null +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithPaginationTitleBubbleCell.swift @@ -0,0 +1,27 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation + +class VoiceBroadcastIncomingWithPaginationTitleBubbleCell: VoiceBroadcastIncomingBubbleCell { + + override func setupViews() { + super.setupViews() + + roomCellContentView?.showPaginationTitle = true + } + +} diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..4f123da7d --- /dev/null +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.swift @@ -0,0 +1,27 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation + +class VoiceBroadcastIncomingWithoutSenderInfoBubbleCell: VoiceBroadcastIncomingBubbleCell { + + override func setupViews() { + super.setupViews() + + roomCellContentView?.showSenderInfo = false + } + +} diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..72f69e4d7 --- /dev/null +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.swift @@ -0,0 +1,27 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation + +class VoiceBroadcastOutgoingWithPaginationTitleBubbleCell: VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell { + + override func setupViews() { + super.setupViews() + + roomCellContentView?.showPaginationTitle = true + } + +} diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..b149647b6 --- /dev/null +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.swift @@ -0,0 +1,41 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation + +class VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell: VoiceBroadcastBubbleCell, BubbleOutgoingRoomCellProtocol { + + override func setupViews() { + super.setupViews() + + roomCellContentView?.showSenderInfo = false + + // TODO: VB update margins attributes + let leftMargin: CGFloat = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.left + BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.left + let rightMargin: CGFloat = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.right + BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.right + + roomCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin + roomCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin + + self.setupBubbleDecorations() + } + + override func update(theme: Theme) { + super.update(theme: theme) + + self.bubbleBackgroundColor = theme.roomCellOutgoingBubbleBackgroundColor + } +} diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift new file mode 100644 index 000000000..a05f00285 --- /dev/null +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift @@ -0,0 +1,113 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 UIKit + +class VoiceBroadcastBubbleCell: VoiceBroadcastPlainCell { + + // MARK: - Properties + + var bubbleBackgroundColor: UIColor? + + // MARK: - Overrides + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + self.update(theme: ThemeService.shared().theme) + } + + override func setupViews() { + super.setupViews() + + self.setupBubbleBackgroundView() + } + + override func addVoiceBroadcastView(_ voiceBroadcastView: UIView, on contentView: UIView) { + super.addVoiceBroadcastView(voiceBroadcastView, on: contentView) + + self.addBubbleBackgroundViewIfNeeded(for: voiceBroadcastView) + } + + // MARK: - Private + + private func addBubbleBackgroundViewIfNeeded(for voiceBroadcastView: UIView) { + + guard let messageBubbleBackgroundView = self.getBubbleBackgroundView() else { + return + } + + self.addBubbleBackgroundView( messageBubbleBackgroundView, to: voiceBroadcastView) + messageBubbleBackgroundView.backgroundColor = self.bubbleBackgroundColor + } + + private func addBubbleBackgroundView(_ bubbleBackgroundView: RoomMessageBubbleBackgroundView, + to voiceBroadcastView: UIView) { + + // TODO: VB update margins attributes + let topMargin = BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.top + let leftMargin = BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.left + let rightMargin = BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.right + let bottomMargin = BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.bottom + + let topAnchor = voiceBroadcastView.topAnchor + let leadingAnchor = voiceBroadcastView.leadingAnchor + let trailingAnchor = voiceBroadcastView.trailingAnchor + let bottomAnchor = voiceBroadcastView.bottomAnchor + + NSLayoutConstraint.activate([ + bubbleBackgroundView.topAnchor.constraint(equalTo: topAnchor, constant: -topMargin), + bubbleBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -leftMargin), + bubbleBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: rightMargin), + bubbleBackgroundView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: bottomMargin) + ]) + } + + private func setupBubbleBackgroundView() { + let bubbleBackgroundView = RoomMessageBubbleBackgroundView() + self.roomCellContentView?.insertSubview(bubbleBackgroundView, at: 0) + } + + // The extension property MXKRoomBubbleTableViewCell.messageBubbleBackgroundView is not working there even by doing recursion + private func getBubbleBackgroundView() -> RoomMessageBubbleBackgroundView? { + guard let contentView = self.roomCellContentView else { + return nil + } + + let foundView = contentView.subviews.first { view in + return view is RoomMessageBubbleBackgroundView + } + return foundView as? RoomMessageBubbleBackgroundView + } +} + +// MARK: - RoomCellTimestampDisplayable +extension VoiceBroadcastBubbleCell: TimestampDisplayable { + + func addTimestampView(_ timestampView: UIView) { + guard let messageBubbleBackgroundView = self.getBubbleBackgroundView() else { + return + } + messageBubbleBackgroundView.addTimestampView(timestampView) + } + + func removeTimestampView() { + guard let messageBubbleBackgroundView = self.getBubbleBackgroundView() else { + return + } + messageBubbleBackgroundView.removeTimestampView() + } +} diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift new file mode 100644 index 000000000..967f4cef8 --- /dev/null +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift @@ -0,0 +1,65 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation + +class VoiceBroadcastPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCellReadMarkerDisplayable { + + private var voiceBroadcastView: UIView? + private var event: MXEvent? + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + guard let contentView = roomCellContentView?.innerContentView, + let bubbleData = cellData as? RoomBubbleCellData, + let event = bubbleData.events.last, + let voiceBroadcastContent = VoiceBroadcastInfo(fromJSON: event.content), + voiceBroadcastContent.state == VoiceBroadcastInfo.State.started.rawValue, + let view = TimelineVoiceBroadcastProvider.shared.buildTimelineVoiceBroadcastViewForEvent(event) else { + return + } + + self.event = event + self.addVoiceBroadcastView(view, on: contentView) + } + + override func setupViews() { + super.setupViews() + + roomCellContentView?.backgroundColor = .clear + roomCellContentView?.showSenderInfo = true + roomCellContentView?.showPaginationTitle = false + } + + // The normal flow for tapping on cell content views doesn't work for bubbles without attributed strings + override func onContentViewTap(_ sender: UITapGestureRecognizer) { + guard let event = self.event else { + return + } + + delegate.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: [kMXKRoomBubbleCellEventKey: event]) + } + + func addVoiceBroadcastView(_ voiceBroadcastView: UIView, on contentView: UIView) { + + self.voiceBroadcastView?.removeFromSuperview() + contentView.vc_addSubViewMatchingParent(voiceBroadcastView) + self.voiceBroadcastView = voiceBroadcastView + } +} + +extension VoiceBroadcastPlainCell: RoomCellThreadSummaryDisplayable {} diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithPaginationTitlePlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithPaginationTitlePlainCell.swift new file mode 100644 index 000000000..fa3c3bc50 --- /dev/null +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithPaginationTitlePlainCell.swift @@ -0,0 +1,27 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation + +class VoiceBroadcastWithPaginationTitlePlainCell: VoiceBroadcastPlainCell { + + override func setupViews() { + super.setupViews() + + roomCellContentView?.showPaginationTitle = true + } + +} diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithoutSenderInfoPlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithoutSenderInfoPlainCell.swift new file mode 100644 index 000000000..6f3ec9110 --- /dev/null +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithoutSenderInfoPlainCell.swift @@ -0,0 +1,27 @@ +// +// Copyright 2022 New Vector Ltd +// +// 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 Foundation + +class VoiceBroadcastWithoutSenderInfoPlainCell: VoiceBroadcastPlainCell { + + override func setupViews() { + super.setupViews() + + roomCellContentView?.showSenderInfo = false + } + +} diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.h b/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.h index 73f0d8f8b..9f18a71d9 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.h +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.h @@ -32,6 +32,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)registerLocationCellsForTableView:(UITableView*)tableView; +- (void)registerVoiceBroadcastCellsForTableView:(UITableView*)tableView; + #pragma mark - Mapping - (NSDictionary*)incomingTextMessageCellsMapping; @@ -54,6 +56,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSDictionary*)locationCellsMapping; +- (NSDictionary*)voiceBroadcastCellsMapping; + @end NS_ASSUME_NONNULL_END diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m b/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m index f016761dd..db11457d7 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m +++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m @@ -112,6 +112,8 @@ [self registerLocationCellsForTableView:tableView]; [self registerFileWithoutThumbnailCellsForTableView:tableView]; + + [self registerVoiceBroadcastCellsForTableView:tableView]; [tableView registerClass:RoomEmptyBubbleCell.class forCellReuseIdentifier:RoomEmptyBubbleCell.defaultReuseIdentifier]; @@ -270,6 +272,13 @@ [tableView registerClass:FileWithoutThumbnailWithPaginationTitlePlainCell.class forCellReuseIdentifier:FileWithoutThumbnailWithPaginationTitlePlainCell.defaultReuseIdentifier]; } +- (void)registerVoiceBroadcastCellsForTableView:(UITableView*)tableView +{ + [tableView registerClass:VoiceBroadcastPlainCell.class forCellReuseIdentifier:VoiceBroadcastPlainCell.defaultReuseIdentifier]; + [tableView registerClass:VoiceBroadcastWithoutSenderInfoPlainCell.class forCellReuseIdentifier:VoiceBroadcastWithoutSenderInfoPlainCell.defaultReuseIdentifier]; + [tableView registerClass:VoiceBroadcastWithPaginationTitlePlainCell.class forCellReuseIdentifier:VoiceBroadcastWithPaginationTitlePlainCell.defaultReuseIdentifier]; +} + #pragma mark Cell class association - (NSDictionary*)buildCellClasses @@ -327,6 +336,9 @@ NSDictionary *locationCellsMapping = [self locationCellsMapping]; [cellClasses addEntriesFromDictionary:locationCellsMapping]; + + NSDictionary *voiceBroadcastCellsMapping = [self voiceBroadcastCellsMapping]; + [cellClasses addEntriesFromDictionary:voiceBroadcastCellsMapping]; NSDictionary *othersCells = @{ @(RoomTimelineCellIdentifierEmpty) : RoomEmptyBubbleCell.class, @@ -550,5 +562,19 @@ }; } +- (NSDictionary*)voiceBroadcastCellsMapping +{ + return @{ + // Incoming + @(RoomTimelineCellIdentifierIncomingVoiceBroadcast) : VoiceBroadcastPlainCell.class, + @(RoomTimelineCellIdentifierIncomingVoiceBroadcastWithoutSenderInfo) : VoiceBroadcastWithoutSenderInfoPlainCell.class, + @(RoomTimelineCellIdentifierIncomingVoiceBroadcastWithPaginationTitle) : VoiceBroadcastWithPaginationTitlePlainCell.class, + // Outoing + @(RoomTimelineCellIdentifierOutgoingVoiceBroadcast) : VoiceBroadcastPlainCell.class, + @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithoutSenderInfo) : VoiceBroadcastWithoutSenderInfoPlainCell.class, + @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithPaginationTitle) : VoiceBroadcastWithPaginationTitlePlainCell.class + }; +} + @end diff --git a/Riot/Modules/Room/Views/InputToolbar/DisabledRoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/DisabledRoomInputToolbarView.m index c3337e887..32859feeb 100644 --- a/Riot/Modules/Room/Views/InputToolbar/DisabledRoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/DisabledRoomInputToolbarView.m @@ -27,7 +27,7 @@ bundle:[NSBundle bundleForClass:[DisabledRoomInputToolbarView class]]]; } -+ (instancetype)roomInputToolbarView ++ (MXKRoomInputToolbarView *)instantiateRoomInputToolbarView { if ([[self class] nib]) { diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index 1f889bb9d..72341ff2a 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -33,6 +33,16 @@ typedef NS_ENUM(NSUInteger, RoomInputToolbarViewSendMode) }; +@protocol RoomInputToolbarViewProtocol + +@property (nonatomic, strong) NSString *eventSenderDisplayName; +@property (nonatomic, assign) RoomInputToolbarViewSendMode sendMode; +- (void)setVoiceMessageToolbarView:(UIView *)voiceMessageToolbarView; +- (CGFloat)toolbarHeight; + + +@end + @protocol RoomInputToolbarViewDelegate /** @@ -40,7 +50,7 @@ typedef NS_ENUM(NSUInteger, RoomInputToolbarViewSendMode) @param toolbarView the room input toolbar view */ -- (void)roomInputToolbarViewDidTapCancel:(RoomInputToolbarView*)toolbarView; +- (void)roomInputToolbarViewDidTapCancel:(MXKRoomInputToolbarView*)toolbarView; /** Inform the delegate that the text message has changed. diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index f3b93ad44..cd9195516 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -30,7 +30,7 @@ static const NSTimeInterval kActionMenuAttachButtonAnimationDuration = .4; static const NSTimeInterval kActionMenuContentAlphaAnimationDuration = .2; static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; -@interface RoomInputToolbarView() +@interface RoomInputToolbarView() @property (nonatomic, weak) IBOutlet UIView *mainToolbarView; @@ -59,7 +59,7 @@ static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; @implementation RoomInputToolbarView @dynamic delegate; -+ (instancetype)roomInputToolbarView ++ (MXKRoomInputToolbarView *)instantiateRoomInputToolbarView { UINib *nib = [UINib nibWithNibName:NSStringFromClass([RoomInputToolbarView class]) bundle:nil]; return [nib instantiateWithOwner:nil options:nil].firstObject; @@ -85,25 +85,6 @@ static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; self.textView.inputAccessoryView = inputAccessoryViewForKeyboard; } -- (void)setVoiceMessageToolbarView:(UIView *)voiceMessageToolbarView -{ - if (voiceMessageToolbarView) { - _voiceMessageToolbarView = voiceMessageToolbarView; - self.voiceMessageToolbarView.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:self.voiceMessageToolbarView]; - - [NSLayoutConstraint activateConstraints:@[[self.mainToolbarView.topAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.topAnchor], - [self.mainToolbarView.leftAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.leftAnchor], - [self.mainToolbarView.bottomAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.bottomAnchor], - [self.mainToolbarView.rightAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.rightAnchor]]]; - } - else - { - [self.voiceMessageToolbarView removeFromSuperview]; - _voiceMessageToolbarView = nil; - } -} - #pragma mark - Override MXKView -(void)customizeViewRendering @@ -543,4 +524,28 @@ static const NSTimeInterval kActionMenuComposerHeightAnimationDuration = .3; }]; } +#pragma mark - RoomInputToolbarViewProtocol + +- (CGFloat)toolbarHeight { + return self.mainToolbarHeightConstraint.constant; +} + +- (void)setVoiceMessageToolbarView:(UIView *)voiceMessageToolbarView +{ + if (voiceMessageToolbarView) { + _voiceMessageToolbarView = voiceMessageToolbarView; + self.voiceMessageToolbarView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:self.voiceMessageToolbarView]; + + [NSLayoutConstraint activateConstraints:@[[self.mainToolbarView.topAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.topAnchor], + [self.mainToolbarView.leftAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.leftAnchor], + [self.mainToolbarView.bottomAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.bottomAnchor], + [self.mainToolbarView.rightAnchor constraintEqualToAnchor:self.voiceMessageToolbarView.rightAnchor]]]; + } + else + { + [self.voiceMessageToolbarView removeFromSuperview]; + _voiceMessageToolbarView = nil; + } +} @end diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib index ff1cee7be..16118e659 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.xib @@ -1,9 +1,9 @@ - + - + @@ -27,7 +27,7 @@ -