Merge branch 'develop' into andy/5619_home_reloads

This commit is contained in:
Andy Uhnak 2022-04-07 10:49:15 +01:00
commit 3531313edc
182 changed files with 7162 additions and 901 deletions

View file

@ -1,3 +1,67 @@
## Changes in 1.8.12 (2022-04-06)
🐛 Bugfixes
- RecentsViewController: Room context preview dismissed unexpectedly ([#5992](https://github.com/vector-im/element-ios/issues/5992))
- Notifications: Strings now fall back to English if they're missing for the current language. ([#5996](https://github.com/vector-im/element-ios/issues/5996))
## Changes in 1.8.11 (2022-04-05)
✨ Features
- RoomViewController: Display threads notice if not displayed before. ([#5770](https://github.com/vector-im/element-ios/issues/5770))
- Addded support for Apple context menus in matrix items list screens ([#5953](https://github.com/vector-im/element-ios/issues/5953))
🙌 Improvements
- Upgrade MatrixSDK version ([v0.23.2](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.23.2)).
- Threads: Strip `ìn reply to` from thread summaries and latest messages. ([#5488](https://github.com/vector-im/element-ios/issues/5488))
- Room: New loading indicators when joining room ([#5604](https://github.com/vector-im/element-ios/issues/5604))
- Room: New loading indicators when creating a room ([#5606](https://github.com/vector-im/element-ios/issues/5606))
- Location Sharing: Update UI on location sharing view ([#5720](https://github.com/vector-im/element-ios/issues/5720))
- Update suggested room preview to behave the same way in all cases ([#5771](https://github.com/vector-im/element-ios/issues/5771))
- RoomViewController: Enable thread menu option and display opt-in screen if threads disabled. ([#5772](https://github.com/vector-im/element-ios/issues/5772))
- Add "Invite people" to the space menu in the left panel and update menu order ([#5810](https://github.com/vector-im/element-ios/issues/5810))
- Allow empty Jitsi default URL in BuildSettings ([#5837](https://github.com/vector-im/element-ios/issues/5837))
- Location sharing: Add the ability for the user to share static location of a pin anywhere on the map ([#5858](https://github.com/vector-im/element-ios/issues/5858))
- Restrict UI components on authentication screen to readable width ([#5898](https://github.com/vector-im/element-ios/issues/5898))
🐛 Bugfixes
- Fixed the regular expression used for link detection in attributed strings. ([#5926](https://github.com/vector-im/element-ios/pull/5926))
- Jitsi: fix app not leaving call when widget is removed ([#1575](https://github.com/vector-im/element-ios/issues/1575))
- Space preview shows wrong number of members ([#4842](https://github.com/vector-im/element-ios/issues/4842))
- Room: Enable joining a room via identifier from another home server ([#4858](https://github.com/vector-im/element-ios/issues/4858))
- MXKRoomDataSource: Fix retain cycle ([#5058](https://github.com/vector-im/element-ios/issues/5058))
- Sync Spaces order with web ([#5134](https://github.com/vector-im/element-ios/issues/5134))
- Fix “It is not possible to join an empty room” on some suggested rooms. ([#5170](https://github.com/vector-im/element-ios/issues/5170))
- Fixed "Add Space" error message ([#5797](https://github.com/vector-im/element-ios/issues/5797))
- RoomDataSource: Reload thread data source without notifying the screen for the first reply. ([#5838](https://github.com/vector-im/element-ios/issues/5838))
- VoiceMessagePlainCell: Fix cell height by adding missing thread summary displayable conformance. ([#5870](https://github.com/vector-im/element-ios/issues/5870))
- Authentication: Ensure the login button is always visible ([#5875](https://github.com/vector-im/element-ios/issues/5875))
- Threads: Tweaks for design review. ([#5878](https://github.com/vector-im/element-ios/issues/5878))
- Search: prevent crash when searching for rooms ([#5883](https://github.com/vector-im/element-ios/issues/5883))
- Room: Fix typing performance by avoiding expensive UI operations ([#5906](https://github.com/vector-im/element-ios/issues/5906))
- The "Swipe to see all rooms" hint is sometimes presented at the wrong time ([#5911](https://github.com/vector-im/element-ios/issues/5911))
- Push notifications: show space preview if user taps invite notification ([#5915](https://github.com/vector-im/element-ios/issues/5915))
- Fix session handling of the call presenter. ([#5938](https://github.com/vector-im/element-ios/issues/5938))
- m.room.join_rules not properly set for private access ([#5943](https://github.com/vector-im/element-ios/issues/5943))
- Fix for app occasionally getting stuck during launch after Login/Register. ([#5948](https://github.com/vector-im/element-ios/issues/5948))
⚠️ API Changes
- Remove unused Bindings in RoundedBorderTextField/Editor ([#5910](https://github.com/vector-im/element-ios/pull/5910))
🗣 Translations
- Translations: Enable all languages rather than waiting for an 80% translation. RTL languages are still disabled due to layout and formatting bugs. ([#5935](https://github.com/vector-im/element-ios/issues/5935))
🚧 In development 🚧
- Onboarding: Add celebration screen after display name and avatar screens. ([#5651](https://github.com/vector-im/element-ios/issues/5651))
## Changes in 1.8.10 (2022-03-31)
🐛 Bugfixes

View file

@ -15,5 +15,5 @@
//
// Version
MARKETING_VERSION = 1.8.11
CURRENT_PROJECT_VERSION = 1.8.11
MARKETING_VERSION = 1.8.13
CURRENT_PROJECT_VERSION = 1.8.13

View file

@ -294,6 +294,8 @@ final class BuildSettings: NSObject {
static let settingsSecurityScreenShowCryptographyInfo:Bool = true
static let settingsSecurityScreenShowCryptographyExport:Bool = true
static let settingsSecurityScreenShowAdvancedUnverifiedDevices:Bool = true
/// A setting to enable the presence configuration settings section.
static let settingsScreenPresenceAllowConfiguration: Bool = false
// MARK: - Timeline settings
static let roomInputToolbarCompressionMode: MediaCompressionMode = .prompt

View file

@ -13,7 +13,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.23.1'
$matrixSDKVersion = '= 0.23.2'
# $matrixSDKVersion = :local
# $matrixSDKVersion = { :branch => 'develop'}
# $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } }

View file

@ -56,16 +56,16 @@ PODS:
- LoggerAPI (1.9.200):
- Logging (~> 1.1)
- Logging (1.4.0)
- MatrixSDK (0.23.1):
- MatrixSDK/Core (= 0.23.1)
- MatrixSDK/Core (0.23.1):
- MatrixSDK (0.23.2):
- MatrixSDK/Core (= 0.23.2)
- MatrixSDK/Core (0.23.2):
- AFNetworking (~> 4.0.0)
- GZIP (~> 1.3.0)
- libbase58 (~> 0.1.4)
- OLMKit (~> 3.2.5)
- Realm (= 10.16.0)
- SwiftyBeaver (= 1.9.5)
- MatrixSDK/JingleCallStack (0.23.1):
- MatrixSDK/JingleCallStack (0.23.2):
- JitsiMeetSDK (= 3.10.2)
- MatrixSDK/Core
- OLMKit (3.2.5):
@ -115,8 +115,8 @@ DEPENDENCIES:
- KeychainAccess (~> 4.2.2)
- KTCenterFlowLayout (~> 1.3.1)
- libPhoneNumber-iOS (~> 0.9.13)
- MatrixSDK (= 0.23.1)
- MatrixSDK/JingleCallStack (= 0.23.1)
- MatrixSDK (= 0.23.2)
- MatrixSDK/JingleCallStack (= 0.23.2)
- OLMKit
- PostHog (~> 1.4.4)
- ReadMoreTextView (~> 3.0.1)
@ -208,7 +208,7 @@ SPEC CHECKSUMS:
libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75
LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d
Logging: beeb016c9c80cf77042d62e83495816847ef108b
MatrixSDK: 54d16aa08f3043fb1bcf639ef1ac5c589100f39f
MatrixSDK: ab6cbb1e8c437eac4f999be516b5e76c2bca9f6a
OLMKit: 9fb4799c4a044dd2c06bda31ec31a12191ad30b5
PostHog: 4b6321b521569092d4ef3a02238d9435dbaeb99f
ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d
@ -225,6 +225,6 @@ SPEC CHECKSUMS:
zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 820f04e07aa252459ecfa88d04da729daca4fcbb
PODFILE CHECKSUM: 677ee0df159e298fde825885ea85e4ebfa8e32fd
COCOAPODS: 1.11.2

View file

@ -1,4 +1,5 @@
<svg width="71" height="70" viewBox="0 0 71 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M36 70C55.33 70 71 54.33 71 35C71 15.67 55.33 0 36 0C16.67 0 1 15.67 1 35C1 54.33 16.67 70 36 70Z" fill="#0DBD8B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M45.1655 26.3399C45.1655 34.8426 38.2529 41.7356 29.7259 41.7356C27.3129 41.7356 25.0291 41.1835 22.9947 40.1992L17.4268 41.9342C15.3689 42.5755 13.4328 40.6542 14.0666 38.6L15.7975 32.9908C14.8289 30.9776 14.2864 28.7219 14.2864 26.3399C14.2864 17.8372 21.1989 10.9443 29.7259 10.9443C38.2529 10.9443 45.1655 17.8372 45.1655 26.3399ZM33.1655 46.3665C31.1238 46.3665 29.175 45.9694 27.392 45.2478C29.984 50.0978 35.0976 53.3976 40.9822 53.3976C43.3876 53.3976 45.6641 52.8464 47.6923 51.8631L53.2379 53.5958C55.2953 54.2383 57.2329 52.3187 56.6003 50.264L54.8736 44.6536C55.8394 42.641 56.3804 40.3857 56.3804 38.0043C56.3804 32.1754 53.1396 27.1032 48.361 24.4902C49.4394 26.4605 50.062 28.6571 50.062 30.9731C50.062 39.4747 41.6697 46.3665 33.1655 46.3665Z" fill="white"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 70 70" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M35,70C54.33,70 70,54.33 70,35C70,15.67 54.33,0 35,0C15.67,0 0,15.67 0,35C0,54.33 15.67,70 35,70ZM32.166,46.367C30.124,46.367 28.175,45.969 26.392,45.248C28.984,50.098 34.098,53.398 39.982,53.398C42.388,53.398 44.664,52.846 46.692,51.863L52.238,53.596C54.295,54.238 56.233,52.319 55.6,50.264L53.874,44.654C54.839,42.641 55.38,40.386 55.38,38.004C55.38,32.175 52.14,27.103 47.361,24.49C48.439,26.461 49.062,28.657 49.062,30.973C49.062,39.475 40.67,46.367 32.166,46.367ZM44.166,26.34C44.166,34.843 37.253,41.736 28.726,41.736C26.313,41.736 24.029,41.184 21.995,40.199L16.427,41.934C14.369,42.576 12.433,40.654 13.067,38.6L14.798,32.991C13.829,30.978 13.286,28.722 13.286,26.34C13.286,17.837 20.199,10.944 28.726,10.944C37.253,10.944 44.166,17.837 44.166,26.34Z" style="fill:rgb(13,189,139);"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,26 @@
{
"images" : [
{
"filename" : "location_center_map_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "location_center_map_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "location_center_map_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -5,3 +5,5 @@
"NSPhotoLibraryUsageDescription" = "Galerie se používá k posílání obrázků a videí.";
"NSCalendarsUsageDescription" = "Zobrazuje události v aplikaci.";
"NSFaceIDUsageDescription" = "Face ID se používá k přístupu do aplikace.";
"NSLocationWhenInUseUsageDescription" = "Když sdílíte svou polohu s lidmi, Element potřebuje přístup, aby jim mohl zobrazit mapu.";
"NSMicrophoneUsageDescription" = "Element vyžaduje přístup k mikrofonu vašeho zařízení pro zahajování a přijímání hovorů, pořizování videa a nahrávání hlasových zpráv.";

View file

@ -50,7 +50,7 @@
"auth_optional_phone_placeholder" = "Číslo mobilního telefonu (nepovinné)";
"auth_phone_placeholder" = "Telefonní číslo";
"auth_repeat_password_placeholder" = "Zopakovat heslo";
"auth_repeat_new_password_placeholder" = "Potvrďte své nové heslo";
"auth_repeat_new_password_placeholder" = "Potvrďte své nové heslo k účtu Matrix";
"auth_home_server_placeholder" = "URL (např. https://matrix.org)";
"auth_identity_server_placeholder" = "URL (např. https://vector.im)";
"auth_invalid_login_param" = "Nesprávné uživatelské jméno nebo heslo";
@ -69,13 +69,13 @@
"auth_untrusted_id_server" = "Server identit není důvěryhodný";
"auth_password_dont_match" = "Hesla se neshodují";
"auth_username_in_use" = "Uživatelské jméno je použito";
"auth_forgot_password" = "Zapomenuté heslo?";
"auth_forgot_password" = "Zapomenuté heslo k účtu Matrix?";
"auth_email_not_found" = "Chyba odeslání emailu: Tato emailová adresa nebyla nalezena";
"auth_use_server_options" = "Použít vlastní možnosti serveru (pokročilé)";
"auth_email_validation_message" = "Prosím zkontrolujte své emaily, abyste mohli pokračovat v registraci";
"auth_msisdn_validation_title" = "Čeká na ověření";
"auth_recaptcha_message" = "Tento domovský server by se rád přesvědčil, že nejste robot";
"auth_reset_password_message" = "K resetování hesla vložte e-mailovou adresu spojenou s vaším účtem:";
"auth_reset_password_message" = "K resetování hesla Matrix účtu vložte e-mailovou adresu spojenou s vaším účtem:";
"auth_reset_password_missing_email" = "Musíte zadat emailovou adresu spojenou s vaším účtem.";
"auth_reset_password_missing_password" = "Musíte zadat nové heslo.";
"auth_reset_password_email_validation_message" = "Na adresu %@ byla odeslána zpráva. Potom, co přejdete na odkaz z této zprávy, klikněte níže.";
@ -95,7 +95,7 @@
"room_creation_keep_private" = "Zachovat jako soukromí";
"room_creation_make_private" = "Nastavit jako soukromé";
"room_creation_wait_for_creation" = "Místnost byla již vytvořena. Prosím vyčkejte.";
"room_creation_invite_another_user" = "Hledat / Pozvat podle uživatelského ID, jména nebo emailu";
"room_creation_invite_another_user" = "Uživatelské ID, jméno, nebo email";
// Room recents
"room_recents_directory_section" = "ADRESÁŘ MÍSTNOSTÍ";
"room_recents_directory_section_network" = "Síť";
@ -236,7 +236,7 @@
"settings_config_user_id" = "Přihlášen/a jako %@";
"auth_msisdn_validation_message" = "Odeslali jsme vám SMS aktivační kod. Prosím, zadejte jej níže.";
"auth_msisdn_validation_error" = "Nelze ověřit telefonní číslo.";
"auth_reset_password_success_message" = "Vaše heslo bylo úspěšně resetováno.\n\nByl jste právě odhlášen na všech vašich relací a nebudete vánm nadále zasíláno oznámení. Pro znovu povolení zasílání oznámení, přihlašte se znovu na každém zařízení.";
"auth_reset_password_success_message" = "Vaše heslo Matrix účtu bylo úspěšně resetováno.\n\nByl jste právě odhlášen na všech vašich relací a nebudou vám nadále zasílána oznámení. Pro opětovné zasílání oznámení se znovu přihlaste na každém zařízení.";
"store_full_description" = "Element je novým typem komunikátoru a propojovací aplikace která:\n\n1. Dává vám kontrolu nad vaším soukromím\n2. Vás nechá komunikovat s kýmkoli v Matrix síti a dokonce mimo ni, díky integrace s aplikacemi jako je například Slack\n3. Vás chrání před reklamou, těžbou Vašich dat, nechráněnými přístupy nebo nezdokumentovanými fukncemi\n4. Zabezpečuje Vaši komunikaci pomocí koncového šifrování s distribuovaným ověřením ostatních\n\nElement se liší od ostatních komunikačních řešeních především tím, že je decentralizovaný a open-source\n\nElement vám umožňuje provozovat vlastní server anebo si vybrat nějakyý z veřejných, takže máte controlu nad vašimi konverzacemi a soukromím. Dává vám přístup do otevřené sítě, takže nejste odkázání jen ke komunikaci s ostatními uživateli Elementu. A je vysoce bezpečný.\n\nElement je toho všeho schopen díky svému operačnímu protokolu - Matrix, otevřeného standartu pro decentralizovanou komunikaci.\n\nElement vás nechává vybrat, kdo bude hostovat vaše konverzace. Přímo z aplikace si můžete vybrat několik rozdílných řešení:\n\n1. Účet zdarma na veřejném serveru matrix.org\n2. Vlastní hosting serveru na vlastním hardwaru\n3. Účet na přizpůsobeném serveru jednodýúchým přihlášením na hosting Element Matrix Services\n\nProč Element?\n\nVLASTNĚTE SVÁ DATA: Vy rozhodujete kde jsou vaše data a zprávy uchovávány. Svá data vlastníte a spravujete Vy, ne nějaká obří korporace, která o vás sbírá osobní data nebo poskytuje přístup dalším stranám.\n\nOTEVŘENÁ KOMUNIKACE A SPOLUPRÁCE: Máte možnost spojit se s kýmkoli v síti Matrix bez ohledu na jeho softwarové řešení, a dokonce se můžete připojit i na jiné komunikační protokoly, jako je Slack, IRC nebo XMPP (Jabber). Komunita podporuje i komunikátory jako Whatsapp, Telegram nebo iMessage.\n\nNEPROLOMITELNÉ ŠIFROVÁNÍ: Skutečné koncové šifrování (pouze přímí účastníci konverzace mají možnost rozšifrovat jejich zprávy) a pokročilé ověřování kontaktů.\n\nVŠESTRANNÉ KOMUNIKAČNÍ MOŽNOSTI: Textové zprávy, hlasové nebo videohovory, přenos souborů, sdílení obrazovky a mnoho dalších funkcí a možností pro implementaci. Vytvářejte místnosti a komunity a zůstaňte v kontaktu.\n\nKDEKOLI SE NACHÁZÍTE: Přístup k plně synchronizované historii konverzací máte kdekoli se nacházíte, ať už z aplikace anebo webového rozhraní na https://element.io/app.";
"user_verification_sessions_list_session_untrusted" = "Nedůvěryhodná";
"user_verification_sessions_list_session_trusted" = "Důvěryhodná";
@ -274,8 +274,8 @@
"auth_autodiscover_invalid_response" = "Neplatná odpověď na objevení domovského serveru";
"auth_accept_policies" = "Přečtěte si a přijměte zásady tohoto domovského serveru:";
"auth_add_email_and_phone_warning" = "Registrace pomocí e-mailu a telefonního čísla najednou ještě není podporována, dokud neexistuje rozhraní API. Zohledněno bude pouze telefonní číslo. Svůj e-mail můžete přidat do svého profilu v nastavení.";
"auth_reset_password_error_is_required" = "Není nakonfigurován žádný server identity: pro obnovení hesla přidejte jeden server z možností .";
"auth_forgot_password_error_no_configured_identity_server" = "Není nakonfigurován žádný server identity: pro obnovení hesla jeden přidejte .";
"auth_reset_password_error_is_required" = "Není nakonfigurován žádný server identity: pro obnovení hesla Matrix účtu přidejte jeden server z možností.";
"auth_forgot_password_error_no_configured_identity_server" = "Není nakonfigurován žádný server identity: pro obnovení hesla k Matrix účtu jeden přidejte.";
"settings_key_backup_button_delete" = "Smazat zálohu";
"settings_key_backup_button_restore" = "Obnovit ze zálohy";
"auth_login_single_sign_on" = "Přihlásit se";
@ -345,3 +345,36 @@
"network_offline_prompt" = "Zdá se, že připojení k internetu je offline.";
"yesterday" = "Včera";
"today" = "Dnes";
"people_empty_view_title" = "Lidé";
"room_recents_unknown_room_error_message" = "Nelze najít místnost. Zkontrolujte, jestli existuje";
"room_recents_join_room_prompt" = "Napište ID nebo alias místnosti";
"room_recents_suggested_rooms_section" = "DOPORUČENÉ MÍSTNOSTI";
"auth_phone_is_required" = "Nelze přidat telefonní číslo pro obnovení hesla k vašemu Matrix účtu, protože není nastaven žádný server identity.";
"auth_email_is_required" = "Nelze přidat emailovou adresu pro obnovení hesla k vašemu Matrix účtu, protože není nastaven žádný server identity.";
"onboarding_use_case_existing_server_button" = "Připojit k serveru";
"onboarding_use_case_existing_server_message" = "Chcete se připojit k již existujícímu serveru?";
"onboarding_use_case_skip_button" = "přeskočit tuto otázku";
/* The placeholder string contains onboarding_use_case_skip_button as a tappable action */
"onboarding_use_case_not_sure_yet" = "Ještě si nejste jistí? Můžete %@";
"onboarding_use_case_community_messaging" = "Komunity";
"onboarding_use_case_work_messaging" = "Týmy";
"onboarding_use_case_personal_messaging" = "Přátelé a rodina";
"onboarding_use_case_message" = "Pomůžeme vám navázat kontakty.";
"onboarding_use_case_title" = "S kým si budete nejvíce psát?";
"onboarding_splash_page_4_message" = "Element je také skvělý na pracovišti. Důvěřují mu nejbezpečnější světové organizace.";
"onboarding_splash_page_4_title_no_pun" = "Zasílání zpráv pro váš tým.";
"onboarding_splash_page_3_message" = "End-to-end šifrování a bez potřeby telefonního čísla. Žádné reklamy nebo zneužívání údajů.";
"onboarding_splash_page_2_message" = "Volba, kde jsou vaše konverzace uloženy, vám dává kontrolu a nezávislost. Spojeno pomocí Matrix.";
"onboarding_splash_page_1_message" = "Bezpečná a nezávislá komunikace, která vám zajistí stejnou úroveň soukromý, jako konverzace z očí do očí ve vašem vlastním domě.";
"onboarding_splash_page_3_title" = "Zabezpečené zasílání zpráv.";
"onboarding_splash_page_2_title" = "Vy máte kontrolu.";
"onboarding_splash_page_1_title" = "Vlastněte své konverzace.";
"onboarding_splash_login_button_title" = "Již mám účet";
// Onboarding
"onboarding_splash_register_button_title" = "Vytvořit účet";
"accessibility_button_label" = "tlačítko";
"ok" = "OK";
"done" = "Hotovo";
"open" = "Otevřít";
"enable" = "Povolit";

View file

@ -56,7 +56,7 @@
"auth_missing_email_or_phone" = "Fehlende E-Mail-Adresse oder Telefon-Nummer";
"auth_password_dont_match" = "Passwörter stimmen nicht überein";
"auth_username_in_use" = "Benutzername bereits verwendet";
"auth_forgot_password" = "Passwort des Matrix-Kontos vergessen?";
"auth_forgot_password" = "Passwort deines Matrix-Kontos vergessen?";
"auth_msisdn_validation_title" = "Verifizierung ausstehend";
"auth_msisdn_validation_message" = "Bitte gib unten den Aktivierungs-Code ein, den wir per SMS verschickt haben.";
"auth_msisdn_validation_error" = "Telefonnummer kann nicht verifiziert werden.";
@ -2138,3 +2138,8 @@
"attachment_unsupported_preview_title" = "Vorschau kann nicht angezeigt werden";
"message_reply_to_sender_sent_their_location" = "hat den eigenen Standort geteilt.";
"room_displayname_all_other_members_left" = "%@ (Verlassen)";
"notice_error_unformattable_event" = "** Nachricht kann nicht dargestellt werden. Bitte erstelle einen Bug-Report";
"home_syncing" = "Synchronisiere";
"settings_labs_use_only_latest_user_avatar_and_name" = "Immer aktuelle Profilbilder und Nicknamen anzeigen";
"room_participants_leave_success" = "Raum verlassen";
"room_participants_leave_processing" = "Verlassen";

View file

@ -17,27 +17,6 @@
/** These strings will be ignored by Weblate. Useful for WIP **/
// MARK: Onboarding Personalization WIP
"onboarding_congratulations_title" = "Congratulations!";
"onboarding_congratulations_message" = "Your account %@ has been created.";
"onboarding_congratulations_personalize_button" = "Personalise profile";
"onboarding_congratulations_home_button" = "Take me home";
"onboarding_personalization_save" = "Save and continue";
"onboarding_personalization_skip" = "Skip this step";
"onboarding_display_name_title" = "Choose a display name";
"onboarding_display_name_message" = "This will be shown when you send messages.";
"onboarding_display_name_placeholder" = "Display Name";
"onboarding_display_name_hint" = "You can change this later";
"onboarding_display_name_max_length" = "Your display name must be less than 256 characters";
"onboarding_avatar_title" = "Add a profile picture";
"onboarding_avatar_message" = "You can change this anytime.";
"onboarding_avatar_accessibility_label" = "Profile picture";
"onboarding_celebration_title" = "Youre all set!";
"onboarding_celebration_message" = "Your preferences have been saved.";
"onboarding_celebration_button" = "Let's go";
"image_picker_action_files" = "Choose from files";

View file

@ -118,6 +118,29 @@
"onboarding_use_case_existing_server_message" = "Looking to join an existing server?";
"onboarding_use_case_existing_server_button" = "Connect to server";
"onboarding_congratulations_title" = "Congratulations!";
/* The placeholder string contains the user's matrix ID */
"onboarding_congratulations_message" = "Your account %@ has been created.";
"onboarding_congratulations_personalize_button" = "Personalise profile";
"onboarding_congratulations_home_button" = "Take me home";
"onboarding_personalization_save" = "Save and continue";
"onboarding_personalization_skip" = "Skip this step";
"onboarding_display_name_title" = "Choose a display name";
"onboarding_display_name_message" = "This will be shown when you send messages.";
"onboarding_display_name_placeholder" = "Display Name";
"onboarding_display_name_hint" = "You can change this later";
"onboarding_display_name_max_length" = "Your display name must be less than 256 characters";
"onboarding_avatar_title" = "Add a profile picture";
"onboarding_avatar_message" = "You can change this anytime.";
"onboarding_avatar_accessibility_label" = "Profile picture";
"onboarding_celebration_title" = "Youre all set!";
"onboarding_celebration_message" = "Your preferences have been saved.";
"onboarding_celebration_button" = "Let's go";
// Authentication
"auth_login" = "Log in";
"auth_register" = "Register";
@ -479,6 +502,11 @@ Tap the + to start adding people.";
"threads_notice_title" = "Threads no longer experimental 🎉";
"threads_notice_information" = "All threads created during the experimental period will now be <b>rendered as regular replies</b>.<br/><br/>This will be a one-off transition, as threads are now part of the Matrix specification.";
"threads_notice_done" = "Got it";
"threads_beta_title" = "Threads";
"threads_beta_information" = "Keep discussions organised with threads.\n\nThreads help keep your conversations on-topic and easy to track. ";
"threads_beta_information_link" = "Learn more";
"threads_beta_enable" = "Try it out";
"threads_beta_cancel" = "Not now";
"media_type_accessibility_image" = "Image";
"media_type_accessibility_audio" = "Audio";
@ -722,6 +750,10 @@ Tap the + to start adding people.";
"settings_enable_room_message_bubbles" = "Message bubbles";
"settings_presence" = "Presence";
"settings_presence_offline_mode" = "Offline Mode";
"settings_presence_offline_mode_description" = "If enabled, you will always appear offline to other users, even when using the application.";
// Security settings
"security_settings_title" = "Security";
"security_settings_crypto_sessions" = "MY SESSIONS";

View file

@ -1,7 +1,8 @@
// Permissions usage explanations
"NSCameraUsageDescription" = "La cámara se usa para sacar fotos, vídeos y hacer videollamadas.";
"NSPhotoLibraryUsageDescription" = "La biblioteca de fotos se usa para enviar fotos y vídeos.";
"NSMicrophoneUsageDescription" = "El micrófono se usa para grabar vídeos y realizar llamadas.";
"NSContactsUsageDescription" = "Para mostrarte cuáles de tus contactos ya utilizan Matrix, Element puede enviar las direcciones de correo electrónico y números telefónicos de tu agenda de contactos a tu Servidor de Identidad de Matrix. En los casos que se puede, tu información personal se cifra antes de ser enviada - por favor consulta la política de privacidad de tu Servidor de Identidad.";
"NSMicrophoneUsageDescription" = "Element necesita usar tu micrófono para hacer y recibir llamadas y grabar vídeos y mensajes de voz.";
"NSContactsUsageDescription" = "Element te mostrará tus contactos para que les puedas invitar a una conversación.";
"NSFaceIDUsageDescription" = "Face ID se usa para acceder a tu aplicación.";
"NSCalendarsUsageDescription" = "Mostrar tus reuniones en la aplicación.";
"NSLocationWhenInUseUsageDescription" = "Cuando compartes tu ubicación con otras personas, Element necesita acceso para que puedan verla en el mapa.";

View file

@ -70,3 +70,60 @@
/* New message indicator on a room */
"MESSAGE_IN_X" = "Mensaje en %@";
"MESSAGE_PROTECTED" = "Nuevo mensaje";
/* Group call from user, CallKit caller name */
"GROUP_CALL_FROM_USER" = "%@ (llamada en grupo)";
/* A user added a Jitsi call to a room */
"GROUP_CALL_STARTED" = "Llamada en grupo empezada";
/* A user's membership has updated in an unknown way */
"USER_MEMBERSHIP_UPDATED" = "%@ ha actualizado su perfil";
/* A user has change their avatar */
"USER_UPDATED_AVATAR" = "%@ ha cambiado su foto de perfil";
/* A user has change their name to a new name which we don't know */
"GENERIC_USER_UPDATED_DISPLAYNAME" = "%@ ha cambiado su nombre";
/** Membership Updates **/
/* A user has change their name to a new name */
"USER_UPDATED_DISPLAYNAME" = "%@ ha cambiado su nombre a %@";
/* A user has reacted to a message, but the reaction content is unknown */
"GENERIC_REACTION_FROM_USER" = "%@ ha enviado una reacción";
/** Reactions **/
/* A user has reacted to a message, including the reaction e.g. "Alice reacted 👍". */
"REACTION_FROM_USER" = "%@ ha reaccionado %@";
/* New file message from a specific person, not referencing a room. */
"LOCATION_FROM_USER" = "%@ ha compartido su ubicación";
/* New file message from a specific person, not referencing a room. */
"FILE_FROM_USER" = "%@ ha enviado un archivo %@";
/* New voice message from a specific person, not referencing a room. */
"VOICE_MESSAGE_FROM_USER" = "%@ ha enviado un mensaje de voz";
/* New audio message from a specific person, not referencing a room. */
"AUDIO_FROM_USER" = "%@ ha enviado un audio %@";
/* New video message from a specific person, not referencing a room. */
"VIDEO_FROM_USER" = "%@ ha enviado un vídeo";
/** Media Messages **/
/* New image message from a specific person, not referencing a room. */
"PICTURE_FROM_USER" = "%@ ha enviado una imagen";
/* New message reply from a specific person in a named room. */
"REPLY_FROM_USER_IN_ROOM_TITLE" = "%@ ha respondido en %@";
/* New message reply from a specific person, not referencing a room. */
"REPLY_FROM_USER_TITLE" = "%@ ha respondido";
/** General **/
"NOTIFICATION" = "Notificación";

File diff suppressed because it is too large Load diff

View file

@ -2075,3 +2075,5 @@
"home_syncing" = "Sünkroniseerimine";
"room_participants_leave_success" = "Sa oled jututoast lahkunud";
"room_participants_leave_processing" = "Lahkumine";
"notice_error_unformattable_event" = "** Sõnumi töötlemine ei õnnestu. Palun anna meile sellest veast teada";
"settings_labs_use_only_latest_user_avatar_and_name" = "Sõnumite ajaloos leiduvate kasutajate puhul näita viimati kasutatud tunnuspilti ning nime";

View file

@ -2149,3 +2149,8 @@
"attachment_unsupported_preview_title" = "Prévisualisation impossible";
"room_displayname_all_other_members_left" = "%@ (Quitté)";
"message_reply_to_sender_sent_their_location" = "a partagé sa localisation.";
"settings_labs_use_only_latest_user_avatar_and_name" = "Afficher le dernier avatar et nom des utilisateurs dans lhistorique de messages";
"room_participants_leave_success" = "Parti du salon";
"room_participants_leave_processing" = "Départ";
"notice_error_unformattable_event" = "** Impossible dafficher le message. Merci de signaler une erreur";
"home_syncing" = "Synchronisation";

View file

@ -121,7 +121,7 @@
"ssl_trust" = "בטוח";
"ssl_unexpected_existing_expl" = "התעודה שונתה מזו שאומתה ע\"י הטלפון שלך. זה חריג לחלוטין. מומלץ לך לא לקבל את התעודה החדשה הזו.";
"start_chat" = "התחל צ'אט";
"store_full_description" = "Element היא סוג חדש של אפליקציית הודעות ושיתוף :\n\n1. מאפשרת לך לשלוט ולשמור על פרטיותך.\n2. מאפשרת לך לתקשר עם כל אחד ברשת, ואפילו מעבר לכך ע\"י שילוב אפליקציות כמו Slack\n3. מגנה עליך מפני פרסומות, כריית מידע ופרצות אחוריות.\n4. מגנה עליך ע\"י הצפנה מקצה לקצה, תוך התחברות מוצלבת לווידוא אחרים.\n\nElement היא אפליקצית הודעות ושיתוף השונה לחלוטין מאפליקציות אחרות מכיון שהיא מבוזרת ועובדת בקוד פתוח.\n\nElement מאפשרת לך לעבוד כמשתמש עצמי או לבחור לעבוד כמשתמש אורח, כך שתהיה לך פרטיות, בעלות וניהול של המידע שלך. היא מאפשרת לך גישה פתוחה לרשת כך שאתה לא מדבר רק עם משתמשים אחרים במערכת ה Element. ובנוסף המערכת מאוד מאובטחת.\n\nElement מסוגלת לבצע כל זאת מכיון שהיא עובדת במטריצה - הסטנדרט עבור תקשורת פתוחה ומבוזרת.\n\nElement נותנת לך בקרה ע\"י כך שהיא מאפשרת לך לבחור מי יהיו המשתמשים בחדרי השיחה. מאפליקציית Element אתה יכול לבחור את צורת ההתחברות בשלוש דרכים שונות:\n\n1. קבלת חשבון חינמי בשרת ציבורי matrix.org\n2. הקמת חשבון משתמש עצמי על שרת אישי שלך.\n3. רישום והקמת חשבון על שרת אירוח של מערכת Element\n\nמדוע לבחור ב Element ?\n\nשלוט במידע שלך : אתה מחליט היכן לשמור את המידע שלך וההודעות שלך. אתה הבעלים והמנהל של המידע, ולא איזה ארגון ענק שכורה את המידע ומאפשר גישה לצד שלישי.\n\nפתיחת הודעות וחדרים: אתה יכול לדבר בחדרי הצ'אט עם כל אחד אחר ברשת, בין אם הם משתמשים ב Element או בכל אפליקציית מטריקס, ואפילו אם הם משתמשים במערכת הודעות שונה כגון Slack, IRC או XMPP.\n\nאבטחה מקסימלית: הצפנה קצה לקצה אמתית (רק אלו שנמצאים בשיחה יכולים לפענח את ההודעות), וחתימה מוצלבת ע\"מ לאשר את מכשירי המשתתפים בשיחות.\n\nתקשורת מלאה: הודעות, שיחות קול ווידאו, שיתוף קבצים, שיתוף מסכים, ומבחר של התקנות, בוטים ויישומים. בניית חדרים, קהילות, \"שמירה על קשר\" ומעקב ביצוע משימות.\n\nהיכן שאתה נמצא: הישאר בקשר היכן שאתה נמצא עם סנכרון מלא להודעות היסטוריות בכל המכשירים ובאתר בכתובת : https://element.io/app";
"store_full_description" = "Element היא סוג חדש של אפליקציית הודעות ושיתוף :\n\n1. מאפשרת לך לשלוט ולשמור על פרטיותך\n2. מאפשרת לך לתקשר עם כל אחד ברשת, ואפילו מעבר לכך ע\"י שילוב אפליקציות כמו Slack\n3. מגנה עליך מפני פרסומות, כריית מידע ופרצות אחוריות\n4. מגנה עליך ע\"י הצפנה מקצה לקצה, תוך התחברות מוצלבת לווידוא אחרים\n\nElement היא אפליקצית הודעות ושיתוף השונה לחלוטין מאפליקציות אחרות מכיון שהיא מבוזרת ועובדת בקוד פתוח.\n\nElement מאפשרת לך לעבוד כמשתמש עצמי או לבחור לעבוד כמשתמש אורח כך שתהיה לך פרטיות, בעלות וניהול של המידע שלך. היא מאפשרת לך גישה פתוחה לרשת; כך שאתה לא מדבר רק עם משתמשים אחרים במערכת ה Element. ובנוסף המערכת מאוד מאובטחת.\n\nElement מסוגלת לבצע כל זאת מכיון שהיא עובדת במטריצה - הסטנדרט עבור תקשורת פתוחה ומבוזרת.\n\nElement נותנת לך בקרה ע\"י כך שהיא מאפשרת לך לבחור מי יהיו המשתמשים בחדרי השיחה. מאפליקציית Element אתה יכול לבחור את צורת ההתחברות בשלוש דרכים שונות:\n\n1. קבלת חשבון חינמי בשרת ציבורי matrix.org\n2. הקמת חשבון משתמש עצמי על שרת אישי שלך\n3. רישום והקמת חשבון על שרת אירוח של מערכת Element\n\nמדוע לבחור ב Element?\n\nשלוט במידע שלך: אתה מחליט היכן לשמור את המידע שלך וההודעות שלך. אתה הבעלים והמנהל של המידע, ולא איזה ארגון ענק שכורה את המידע ומאפשר גישה לצד שלישי.\n\nפתיחת הודעות וחדרים: אתה יכול לדבר בחדרי הצ'אט עם כל אחד אחר ברשת, בין אם הם משתמשים ב Element או בכל אפליקציית מטריקס, ואפילו אם הם משתמשים במערכת הודעות שונה כגון Slack, IRC או XMPP.\n\nאבטחה מקסימלית: הצפנה קצה לקצה אמתית (רק אלו שנמצאים בשיחה יכולים לפענח את ההודעות), וחתימה מוצלבת ע\"מ לאשר את מכשירי המשתתפים בשיחות.\n\nתקשורת מלאה: הודעות, שיחות קול ווידאו, שיתוף קבצים, שיתוף מסכים, ומבחר של התקנות, בוטים ויישומים. בניית חדרים, קהילות, \"שמירה על קשר\" ומעקב ביצוע משימות.\n\nהיכן שאתה נמצא: הישאר בקשר היכן שאתה נמצא עם סנכרון מלא להודעות היסטוריות בכל המכשירים שלך ובאתר בכתובת : https://element.io/app.";
"onboarding_splash_login_button_title" = "קיים ברשותי חשבון";
"accessibility_button_label" = "כפתור";
"callbar_only_single_active_group" = "החלק להשתתפות בשיחת הועידה (%@)";
@ -490,3 +490,616 @@
"spaces_empty_space_title" = "למרחב הזה אין עדיין חדרים";
"space_beta_announce_title" = "בקרוב - מרחבים";
"space_feature_unavailable_subtitle" = "מרחבים עדיין לא קיימים עבור iOS, אפשר להשתמש בהם בממשק ה WEB והמחשב";
// Recover with key
"secrets_recovery_with_key_title" = "מפתח אבטחה";
"secrets_recovery_with_passphrase_invalid_passphrase_message" = "אנא וודא שהכנסת ביטוי אבטחה נכון.";
"secrets_recovery_with_passphrase_invalid_passphrase_title" = "לא יכול לגשת לאחסון סודי";
"secrets_recovery_with_passphrase_lost_passphrase_action_part3" = ".";
"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "השתמש במפתח האבטחה שלך";
"secrets_recovery_with_passphrase_lost_passphrase_action_part1" = "לא יודע את ביטוי האבטחה שלך? אתה יכול ";
"secrets_recovery_with_passphrase_recover_action" = "השתמש בביטוי";
"secrets_recovery_with_passphrase_passphrase_placeholder" = "הכנס ביטוי אבטחה";
"secrets_recovery_with_passphrase_passphrase_title" = "הכנס";
"secrets_recovery_with_passphrase_information_verify_device" = "השתמש בביטוי האבטחה שלך על מנת לאמת התקן זה.";
"secrets_recovery_with_passphrase_information_default" = "הכנס את הודעת האבטחה הישנה שלך ואת זיהוי החתימה שלך על מנת לאמת ממשקים אחרים ע\"י הכנסת ביטוי האבטחה שלך.";
// Recover with passphrase
"secrets_recovery_with_passphrase_title" = "ביטוי אבטחה";
"secrets_recovery_reset_action_part_2" = "אתחל הכל";
// MARK: - Secrets recovery
"secrets_recovery_reset_action_part_1" = "אפשרויות שחזור נשכחו או הלכו לאיבוד? ";
"user_verification_session_details_verify_action_other_user" = "מאומת ידנית";
"user_verification_session_details_verify_action_current_user_manually" = "אימות טקסט ידני";
"user_verification_session_details_verify_action_current_user" = "אימות אינטראקטיבי";
"user_verification_session_details_additional_information_untrusted_current_user" = "אם לא התחברת לממשק זה, חשבונך עלול להיחסם.";
"user_verification_session_details_additional_information_untrusted_other_user" = "עד לאישור הממשק ע\"י היוזר, הודעות שנשלחות אליו וממנו יסומנו עם הודעת אזהרה. לחילופין, אתה יכול לאמת אותו ידנית.";
"user_verification_session_details_information_untrusted_other_user" = " התחבר ע\"י שימוש בממשק חדש:";
"user_verification_session_details_information_untrusted_current_user" = "אמת ממשק זה ע\"מ לסמן אותו כבטוח ואפשר לו גישה להודעות מוצפנות:";
"user_verification_session_details_information_trusted_other_user_part2" = " אמת זאת:";
"user_verification_session_details_information_trusted_other_user_part1" = "ממשק זה מהימן עבור הודעות מוצפנות מכיון ";
"user_verification_session_details_information_trusted_current_user" = "ממשק זה מהימן עבור הודעות מוצפנות מכיון שכבר אימתת אותו:";
"user_verification_session_details_untrusted_title" = "לא מהימן";
// Session details
"user_verification_session_details_trusted_title" = "מהימן";
"user_verification_sessions_list_session_untrusted" = "לא מהימן";
"user_verification_sessions_list_session_trusted" = "מהימן";
"user_verification_sessions_list_table_title" = "ממשק";
"user_verification_sessions_list_information" = "הודעות עם משתמש זה בחדר זה מוצפנות מקצה לקצה ואינן ניתנות לקריאה ע\"י צד שלישי.";
"user_verification_sessions_list_user_trust_level_unknown_title" = "לא ידוע";
"user_verification_sessions_list_user_trust_level_warning_title" = "זהירות";
// Sessions list
"user_verification_sessions_list_user_trust_level_trusted_title" = "מהימן";
"user_verification_start_additional_information" = "ע\"מ לשמור על אבטחה, בצע פעולה זו לבד או מצא דרך חלופית ליצור קשר.";
"user_verification_start_waiting_partner" = "ממתין ל %@…";
"user_verification_start_information_part2" = " ע\"י בדיקה באמצעות קוד חד פעמי בשני ההתקנים שלך.";
"user_verification_start_information_part1" = "לטובת אבטחה מקסימלית, אנא אשר. ";
// MARK: - User verification
// Start
"user_verification_start_verify_action" = "התחל אימות";
"key_verification_scan_confirmation_scanned_device_information" = "האם התקן אחר מראה גם את אותה הגנה?";
"key_verification_scan_confirmation_scanned_user_information" = "האם %@ מציג את אותה הגנה?";
// Scanned
"key_verification_scan_confirmation_scanned_title" = "כמעט סיימנו!";
"key_verification_scan_confirmation_scanning_device_waiting_other" = "ממתין להתקן אחר…";
"key_verification_scan_confirmation_scanning_user_waiting_other" = "ממתין ל%@…";
// MARK: Scan confirmation
// Scanning
"key_verification_scan_confirmation_scanning_title" = "כמעט סיימנו! ממתין לאישור…";
"key_verification_verify_qr_code_scan_other_code_success_message" = "קוד QR אומת בהצלחה.";
"auth_add_phone_message_2" = "הגדר מספר טלפון, בהמשך יהווה אפשרות לזיהויך על ידי משתמשים שמכירים אותך.";
"auth_add_email_message_2" = "הגדר דוא\"ל לטובת שחזור החשבון, בהמשך יהווה אפשרות לזיהויך על ידי משתמשים שמכירים אותך.";
"auth_invalid_user_name" = "שמות משתמשים יכולים להכיל רק אותיות, מספרים, נקודות, מקפים וקווים תחתונים";
"auth_home_server_placeholder" = "כתובת אתר (לדוגמה https://matrix.org)";
"auth_user_id_placeholder" = "דואר אלקטרוני או שם משתמש";
"auth_login_single_sign_on" = "התחבר";
"onboarding_use_case_existing_server_button" = "התחבר לשרת";
"onboarding_use_case_existing_server_message" = "מחפשים להצטרף לשרת קיים ?";
"onboarding_use_case_skip_button" = "דלג על השאלה";
/* The placeholder string contains onboarding_use_case_skip_button as a tappable action */
"onboarding_use_case_not_sure_yet" = "מתלבט ? אתה מסוגל %@";
"onboarding_use_case_community_messaging" = "קהילות";
"onboarding_use_case_personal_messaging" = "חברים ומשפחה";
"discard" = "לבטל";
"abort" = "בטל";
"yes" = "כן";
// Action
"no" = "לא";
"login_error_resource_limit_exceeded_contact_button" = "צור קשר עם מנהל המערכת";
"login_error_resource_limit_exceeded_message_contact" = "\n\nאנא צור קשר מנהל השירות שלך על מנת להמשיך להשתמש בשירות זה.";
"login_error_resource_limit_exceeded_message_monthly_active_user" = "שרת זה הגיע למגבלת המשתמשים הפעילים בו.";
"login_error_resource_limit_exceeded_message_default" = "שרת בית זה הגיע לסף של אחד המשאבים שלו.";
"login_error_resource_limit_exceeded_title" = "הגעה לסף משאבים";
"login_desktop_device" = "שולחן עבודה";
"login_tablet_device" = "טאבלט";
"login_mobile_device" = "נייד";
"login_error_forgot_password_is_not_supported" = "שכחתי סיסמה לא נתמך כרגע";
"register_error_title" = "רישום נכשל";
"room_accessibility_upload" = "העלאה";
"room_accessibility_integrations" = "התקנות";
"room_accessibility_search" = "חפש";
"room_message_edits_history_title" = "עריכות הודעה";
"room_resource_usage_limit_reached_message_contact_3" = " על מנת להגיע לעלית סף זה.";
"room_resource_usage_limit_reached_message_2" = "חלק מהמשתמשים לא יוכלו להתחבר.";
"room_resource_usage_limit_reached_message_1_monthly_active_user" = "שרת הבית הזה הגיע לסף החודשי של משתמשים פעילים בו אז ";
"room_resource_usage_limit_reached_message_1_default" = "שרת הבית הזה הגיע לסף של אחד המשאבים שלו אז ";
"room_resource_limit_exceeded_message_contact_3" = " על מנת להמשיך לעשות שימוש בשירות זה.";
"room_resource_limit_exceeded_message_contact_2_link" = "צור קשר עם מנהל השירות שלך";
"room_resource_limit_exceeded_message_contact_1" = " בבקשה ";
"room_predecessor_link" = "לחץ כאן כדי לראות הודעות ישנות.";
"room_predecessor_information" = "חדר זה הוא המשך של שיחה אחרת.";
"room_replacement_link" = "השיחה ממשיכה כאן.";
"room_replacement_information" = "חדר זה הוחלף והוא אינו פעיל יותר.";
"room_action_reply" = "תשובה";
"room_action_send_file" = "שלח קובץ";
"room_action_send_sticker" = "שלח תוית";
"room_action_send_photo_or_video" = "שלח תמונה אם סרטון וידאו";
"room_action_camera" = "צלם תמונה או וידאו";
"room_creation_error_invite_user_by_email_without_identity_server" = "לא הוגדר אף שרת זיהוי לכן אתה לא יכול להוסיף משתתף עם אימייל.";
"room_creation_invite_another_user" = "מספר משתמש, שם או אימייל";
"room_creation_wait_for_creation" = "החדר כבר הוגדר. אנא המתן.";
"room_creation_make_private" = "הפוך לפרטי";
"room_creation_keep_private" = "שמור על פרטיות";
"room_creation_make_public_prompt_msg" = "האם אתה בטוח שברצונך ליצור צ'אט זה ציבורי? כל אחד יוכל לקרא את ההודעות שלך ולהצטרף לצ'אט.";
"room_creation_make_public_prompt_title" = "הגדר צ'אט זה ציבורי?";
"room_creation_make_public" = "הפוך לציבורי";
"room_creation_public_room" = "צ'אט זה הוא ציבורי";
"room_creation_private_room" = "צ'אט זה הוא פרטי";
"room_creation_privacy" = "פרטי";
"room_creation_appearance_picture" = "תמונת צ'אט(אופציונלי)";
"room_creation_appearance_name" = "שם";
"room_creation_appearance" = "קיים";
"room_creation_account" = "חשבון";
// Chat creation
"room_creation_title" = "צ'אט חדש";
// Errors
"error_user_already_logged_in" = "נראה שאתה מנסה להתחבר לשרת בית אחר. האם אתה מעוניין להתנתק?";
"social_login_button_title_sign_up" = "הירשם באמצעות %@";
"social_login_button_title_sign_in" = "התחבר באמצעות %@";
"social_login_button_title_continue" = "המשך עם %@";
"space_feature_unavailable_information" = "מרחבים הם דרך חדשה לקבץ חדרים ואנשים.\n\nהם יהיו כאן בקרוב. בינתיים, אם תצטרף לאחד בפלטפורמה אחרת, תוכל לגשת לכל חדר אליו אתה מצטרף כאן.";
"leave_space_and_all_rooms_action" = "עזוב את כל החדרים והמרחבים";
"leave_space_only_action" = "אל תעזוב אף אחד מהחדרים";
"leave_space_message_admin_warning" = "אתה עם הרשאות admin במרחב זה, וודא שהעברת הרשאות admin לחבר אחר לפני שאתה עוזב.";
"leave_space_message" = "האתם אתה בטוח שברצונך לעזוב את %@? האם אתה מעוניין לעזוב גם את כל החדרים והמרחבים של מרחב זה?";
"leave_space_title" = "עזוב את %@";
"spaces_home_space_title" = "בית";
"space_beta_announce_information" = "מרחבים הם דרך לקבץ חדרים ואנשים. הם לא ב IOS לבינתיים, אבל אתה יכול להשתמש בהם כעת ב WEB או בשולחן העבודה.";
"space_beta_announce_subtitle" = "הגרסה החדשה של קהילות";
"space_beta_announce_badge" = "בטה";
"spaces_explore_rooms" = "חשוף חדרים";
"ssl_fingerprint_hash" = "טביעת אצבע (%@):";
"call_transfer_to_user" = "העבר ל %@";
"call_consulting_with_user" = "התייעצות עם %@";
"call_video_with_user" = "שיחת וידאו באמצעות %@";
"call_voice_with_user" = "חיוג קולי באמצעות %@";
"call_more_actions_dialpad" = "מסך חיוג";
"call_more_actions_transfer" = "העבר";
"call_more_actions_audio_use_device" = "מכשיר רמקול";
"call_more_actions_change_audio_device" = "החלף התקן שמע";
"call_more_actions_unhold" = "התחל שוב";
"call_more_actions_hold" = "החזק";
"call_holded" = "אתה מחזיק את השיחה";
"call_remote_holded" = "%@ מחזיק את השיחה";
"call_invite_expired" = "פג תוקף הזמנת שיחה";
"incoming_voice_call" = "שיחת קול נכנסת";
"incoming_video_call" = "שיחת וידאו נכנסת";
"call_ended" = "שיחה הסתיימה";
"call_ringing" = "מצלצל…";
// Settings keys
// call string
"call_connecting" = "מתחבר…";
// gcm section
"settings_config_identity_server" = "זהות שרת:%@";
"notification_settings_notify_all_other" = "עדכן עבור כל ההודעות/החדרים האחרים";
"notification_settings_by_default" = "בברירת מחדל...";
"notification_settings_suppress_from_bots" = "השתק התראות מבוטים";
"notification_settings_receive_a_call" = "עדכן אותי כאשר אני מקבל שיחה";
"notification_settings_people_join_leave_rooms" = "עדכן אותי כאשר אנשים מצטרפים או עוזבים חדרים";
"notification_settings_invite_to_a_new_room" = "עדכן אותי כאשר אני מוזמן לחדר חדש";
"notification_settings_just_sent_to_me" = "עדכן אותי עם צליל עבור הודעות שנשלחות רק אליי";
"notification_settings_contain_my_display_name" = "עדכן אותי עם צליל לגבי הודעות הכוללות תצוגת שמי";
"notification_settings_contain_my_user_name" = "עדכן אותי עם צליל לגבי הודעות הכוללות את שם המשתמש שלי";
"notification_settings_other_alerts" = "התראות אחרות";
"notification_settings_highlight" = "דבר חשוב";
"notification_settings_global_info" = "התראת הגדרות עבור החשבון שלך נשמרה ותשותף עם כל ההתקנים שתומכים בה(כולל התראות מסך).\n\nחוקים יושמו; החוק הראשון שיותאם יגדיר את התוצאה שתישלח כהודעה.\nאז:התראות עבור מלה חשובות יותר מהתראות עבור חדר שחשובות יותר מהתראות שולח.\nעבור מספר חוקים מאותו סוג, הראשון ברשימה שיימצא מתאים יקבל את העדיפות.";
"notification_settings_per_word_notifications" = "התראות מלה";
"notification_settings_enable_notifications_warning" = "ההתראות עבור כל ההתקנים נחסמו כעת.";
"notification_settings_enable_notifications" = "אפשר התראות";
// Notification settings screen
"notification_settings_disable_all" = "בטל את כל ההתראות";
"settings_title_notifications" = "התראות";
// Settings screen
"settings_title_config" = "תצורה";
"settings_integrations" = "התקנה";
"settings_identity_server_settings" = "שרת הזדהות";
"settings_discovery_settings" = "גלוי";
"settings_calls_settings" = "שיחות";
"account_logout_all" = "צא מכל החשבונות";
// Settings
"settings_title" = "הגדרות";
"settings_ui_theme_picker_title" = "בחר נושא";
"settings_ui_theme_black" = "שחור";
"settings_ui_theme_dark" = "חשוך";
"share_extension_low_quality_video_message" = "שלח ב%@ עבור איכות משופרת, או שלח באיכות נמוכה מזו.";
"share_extension_low_quality_video_title" = "וידאו יישלח באיכות נמוכה";
"share_extension_failed_to_encrypt" = "שליחה נכשלה. בדוק באפליקציה הראשית את הגדרות ההצפנה עבור חדר זה";
// Share extension
"share_extension_auth_prompt" = "התחבר אל האפליקציה הראשית על מנת לחלוק תוכן";
"room_widget_permission_room_id_permission" = "מספר חדר";
"room_widget_permission_widget_id_permission" = "מספר יישומון";
"room_widget_permission_theme_permission" = "שם הנושא";
"room_widget_permission_user_id_permission" = "מספר המשתמש שלך";
"room_widget_permission_avatar_url_permission" = "דמות ה URL שלך";
"room_widget_permission_display_name_permission" = "שם התצוגה שלך";
"search_no_result" = "אין תוצאות";
"search_people_placeholder" = "חיפוש באמצעות מספר משתמש, שם או אימייל";
"search_filter_placeholder" = "סינון";
"search_default_placeholder" = "חפש";
"secrets_recovery_with_key_information_verify_device" = "השתמש במפתח האבטחה שלך ע\"מ לאשר התקן זה.";
"secrets_recovery_with_key_information_default" = "היכנס אל ההודעות המאובטחות הישנות שלך ואל זיהוי החתימה שלך ע\"מ לאשר קישורים אחרים ע\"י הכנסת מפתח האבטחה שלך.";
"room_event_action_reaction_history" = "תגובה ישנה";
"room_event_action_reaction_show_less" = "הראה פחות";
"room_event_action_reaction_show_all" = "הראה הכל";
"room_event_action_edit" = "ערוך";
"room_event_action_reply" = "תגובה";
"room_event_action_view_encryption" = "מידע מוצפן";
"room_event_action_cancel_download" = "בטל הורדה";
"room_event_action_cancel_send" = "בטל שליחה";
"room_event_action_delete_confirmation_message" = "האם אתה בטוח שברצונך למחוק הודעה זו שלא נשלחה?";
"room_event_action_delete_confirmation_title" = "מחק הודעה שלא נשלחה";
"room_event_action_delete" = "מחק";
"room_event_action_resend" = "שלח שוב";
"room_event_action_save" = "שמור";
"room_event_action_report_prompt_ignore_user" = "האם אתה רוצה להסתיר את כל ההודעות ממשתמש זה?";
"room_event_action_ban_prompt_reason" = "סיבה לחסימת משתמש זה";
"room_event_action_kick_prompt_reason" = "סיבה להסרת משתמש זה";
"room_event_action_report_prompt_reason" = "סיבה לדיווח על תוכן זה";
"room_event_action_report" = "דווח תוכן";
"room_event_action_view_decrypted_source" = "צפה במקור מפוענח";
"room_event_action_view_source" = "צפה במקור";
"room_event_action_permalink" = "העתק קישור להודעה";
"room_event_action_view_in_room" = "צפה בתוך החדר";
"room_event_action_forward" = "העבר";
"room_event_action_share" = "שתף";
"room_event_action_more" = "עוד";
"room_event_action_redact" = "הסר";
"room_event_action_end_poll" = "סוף הצבעה";
"room_event_action_remove_poll" = "הסר הצבעה";
"room_event_action_quote" = "ציטוט";
"room_event_action_copy" = "העתק";
"room_delete_unsent_messages" = "מחק הודעות שלא נשלחו";
"room_resend_unsent_messages" = "שלח שוב הודעות שלא נשלחו";
"room_prompt_cancel" = "בטל הכל";
"room_prompt_resend" = "שלח שוב הכל";
"room_conference_call_no_power" = "אתה צריך הרשאה על מנת לנהל שיחת ועידה בחדר זה";
"room_ongoing_conference_call_close" = "סגור";
"room_ongoing_conference_call_with_close" = "שיחת ועידה מתמשכת. הצטרף לזה כ%@ או %@. %@.";
"room_ongoing_conference_call" = "שיחת ועידה מתמשכת. הצטרף כ%@ או %@.";
"room_unsent_messages_cancel_message" = "אתה בטוח שברצונך למחוק את כל ההודעות שלא נשלחו בחדר זה?";
"room_unsent_messages_cancel_title" = "מחק הודעות שלא נשלחו";
"room_unsent_messages_unknown_devices_notification" = "הודעה נכשלה להישלח בשל קישור לא ידוע שהוצג.";
"room_unsent_messages_notification" = "הודעות נכשלו להישלח.";
"room_offline_notification" = "החיבור לשרת אבד.";
"room_message_reply_to_short_placeholder" = "שלח תגובה…";
"room_message_short_placeholder" = "שלח הודעה…";
"encrypted_room_message_reply_to_placeholder" = "שלח תגובה מוצפנת…";
"encrypted_room_message_placeholder" = "שלח הודעה מוצפנת…";
"room_do_not_have_permission_to_post" = "אין לך הרשאות לשלוח לחדר זה";
"room_message_replying_to" = "עונה ל%@";
"room_message_editing" = "עורך";
"room_message_unable_open_link_error_message" = "לא יכול לפתוח את הקישור.";
"room_message_reply_to_placeholder" = "שלח תגובה (לא מוצפנת)…";
"room_message_placeholder" = "שלח הודעה (לא מוצפנת)…";
"room_many_users_are_typing" = "%@, %@ וגם אחרים מקלידים…";
"room_two_users_are_typing" = "%@ & %@ מקלידים…";
"room_one_user_is_typing" = "%@ מקליד…";
"room_new_messages_notification" = "%@ הודעות חדשות";
"room_new_message_notification" = "%@ הודעה חדשה";
"room_accessiblity_scroll_to_bottom" = "גלול לתחתית";
"room_jump_to_first_unread" = "עבור ללא נקרא";
// MARK: - Chat
"room_slide_to_end_group_call" = "החלק על מנת לסיים את השיחה לכולם";
"room_member_power_level_short_custom" = "מותאם אישית";
"room_member_power_level_short_moderator" = "מצב";
"room_member_power_level_short_admin" = "מנהל מערכת";
"room_member_power_level_custom_in" = "%@ מותאם אישית ב%@";
"room_member_power_level_moderator_in" = "מנחה ב %@";
"room_member_power_level_admin_in" = "מנהל ב%@";
"room_participants_security_information_room_encrypted_for_dm" = "ההודעות כאן מוצפנות קצה לקצה.\n\nההודעות שלך מאובטחות עם מנעולים ורק לך ולמקבל ההודעות יש מפתחות ייחודיים לפתוח אותם.";
"room_participants_security_information_room_encrypted" = "ההודעות בחדר זה מוצפנות קצה לקצה.\n\nההודעות שלך מאובטחות עם מנעולים ורק לך ולמקבל ההודעות יש מפתח ייחודי לפתוח אותן.";
"room_participants_security_information_room_not_encrypted_for_dm" = "ההודעות כאן אינן מוצפנות קצה לקצה.";
"room_participants_security_information_room_not_encrypted" = "הודעות בחדר זה אינן מוצפנות קצה לקצה.";
"room_participants_security_loading" = "טוען…";
"room_participants_action_security_status_loading" = "טוען…";
"room_participants_action_security_status_warning" = "אזהרה";
"room_participants_action_security_status_complete_security" = "אבטחה מלאה";
"room_participants_action_security_status_verify" = "תאשר";
"room_participants_action_security_status_verified" = "מאומת";
"room_participants_action_mention" = "תזכורת";
"room_participants_action_start_video_call" = "התחל שיחת וידאו";
"room_participants_action_start_voice_call" = "התחל שיחת קול";
"room_participants_action_start_new_chat" = "התחל צ'אט חדש";
"room_participants_action_set_admin" = "הפוך למנהל";
"room_participants_action_set_moderator" = "הפוך למנחה";
"room_participants_action_set_default_power_level" = "אפס הגדרות משתמש לברירת מחדל";
"room_participants_action_unignore" = "הצג כל ההודעות ממשתמש זה";
"room_participants_action_ignore" = "הסתר כל ההודעות ממשתמש זה";
"room_participants_action_unban" = "בטל חסימה";
"room_participants_action_ban" = "אסור מחדר זה";
"room_participants_action_remove" = "הוצא מחדר זה";
"room_participants_action_leave" = "עזוב חדר זה";
"room_participants_action_invite" = "הזמן";
"room_participants_action_section_security" = "אבטחה";
"room_participants_action_section_other" = "אפשרויות";
"room_participants_action_section_devices" = "ממשקים";
"room_participants_action_section_direct_chats" = "צ'אטים ישירים";
"room_participants_action_section_admin_tools" = "כלי מערכת";
"room_participants_ago" = "לפני";
"room_participants_now" = "כעת";
"room_participants_idle" = "פנוי";
"room_participants_unknown" = "לא ידוע";
"room_participants_offline" = "לא זמין";
"room_participants_online" = "זמין";
"room_participants_start_new_chat_error_using_user_email_without_identity_server" = "לא הוגדר שרת זיהוי לכן אתה לא יכול להתחיל צ'אט עם איש קשר באמצעות אימייל.";
"room_participants_invited_section" = "מוזמן";
"room_participants_invite_malformed_id" = "מספר מזהה לא תקין. צריך להיות כתובת אימייל או מזהה מערכת כמו 'localpart:domain@'";
"room_participants_invite_malformed_id_title" = "שגיאת הזמנה";
"room_participants_invite_another_user" = "חפש/הזמן באמצעות מספר מזהה, שם או כתובת מייל";
"room_participants_filter_room_members_for_dm" = "סינון חברים";
"room_participants_filter_room_members" = "סינון חברים בחדר";
"room_participants_invite_prompt_msg" = "האם אתה בטוח שברצונך להזמין את %@ לצ'אט זה?";
"room_participants_invite_prompt_title" = "אישור";
"room_participants_remove_third_party_invite_prompt_msg" = "האם אתה בטוח שברצונך לבטל הזמנה זו?";
"room_participants_remove_prompt_msg" = "האם אתה בטוח שברצונך להסיר את %@ מצ'אט זה?";
"room_participants_remove_prompt_title" = "אישור";
"room_participants_leave_success" = "עזב את החדר";
"room_participants_leave_processing" = "עוזב";
"room_participants_leave_prompt_msg_for_dm" = "האם אתה בטוח שברצונך לעזוב?";
"room_participants_leave_prompt_msg" = "האם אתה בטוח שברצונך לעזוב את החדר?";
"room_participants_leave_prompt_title_for_dm" = "עזוב";
"room_participants_leave_prompt_title" = "עזוב חדר";
"room_participants_multi_participants" = "%@ משתתפים";
"room_participants_one_participant" = "משתתף 1";
"room_participants_add_participant" = "הוסף משתתף";
// Chat participants
"room_participants_title" = "משתתפים";
"find_your_contacts_identity_service_error" = "לא ניתן להתחבר לשרת הזיהוי.";
"find_your_contacts_footer" = "ניתן לחסום זאת בכל זמן דרך תפריט הגדרות.";
"find_your_contacts_button_title" = "חפש את אנשי הקשר שלך";
"find_your_contacts_message" = "אפשר ל%@ להציג את רשימת אנשי הקשר שלך על מנת שתוכל לפתוח צ'אט במהירות עם אלה שאתה מכיר הכי טוב.";
"find_your_contacts_title" = "התחל ע\"י הצגת רשימת אנשי הקשר שלך";
"contacts_user_directory_offline_section" = "ספריית משתמש (במצב לא מקוון)";
"contacts_user_directory_section" = "ספריית משתמש";
"contacts_address_book_permission_denied_alert_message" = "על מנת לאפשר גישה לרשימת אנשי קשר, עבור להגדרות המכשיר שלך.";
"contacts_address_book_permission_denied_alert_title" = "גישה לרשימת אנשי קשר נחסמה";
"contacts_address_book_permission_denied" = "לא אישרת ל %@ גישה לרשימת אנשי הקשר שלך";
"contacts_address_book_permission_required" = "נדרשת הרשאה עבור גישה לרשימת אנשי הקשר";
"contacts_address_book_no_contact" = "אין אנשי קשר לוקליים";
"contacts_address_book_no_identity_server" = "לא הוגדר שרת זיהוי";
"contacts_address_book_matrix_users_toggle" = "משתמשי מטריצה בלבד";
// Contacts
"contacts_address_book_section" = "אנשי קשר מקומיים";
"directory_searching_title" = "מחפש ספריות…";
"directory_search_results_more_than" = ">% תוצאות נמצאו עבור %@";
"directory_search_results" = "% תוצאות נמצאו עבור %@";
"directory_search_results_title" = "דפדף תוצאות ספרייה";
"directory_cell_description" = "% חדרים";
// Directory
"directory_cell_title" = "דפדף תיקייה";
"search_in_progress" = "מחפש…";
"search_files" = "קבצים";
"search_people" = "אנשים";
"search_messages" = "הודעות";
// Search
"search_rooms" = "חדרים";
"group_section" = "קהילות";
// Groups tab
"group_invite_section" = "הזמנות";
"rooms_empty_view_information" = "חדרים הם דבר נפלא עבור כל קבוצת צ'אט , פרטית או ציבורית. הקש + על מנת לחפש חדרים קיימים, או צור חדשים.";
"rooms_empty_view_title" = "חדרים";
// Rooms tab
"room_directory_no_public_room" = "אין חדרים ציבוריים זמינים";
"people_empty_view_information" = "השתמש בצ'אט בצורה בטוחה עם כל אחד. הקש + על מנת להוסיף אנשים.";
"people_empty_view_title" = "אנשים";
"people_no_conversation" = "אין שיחות";
"people_conversation_section" = "שיחות";
// People tab
"people_invites_section" = "הזמנות";
"room_recents_unknown_room_error_message" = "חדר זה לא נמצא.וודא האם הוא קיים";
"room_recents_join_room_prompt" = "הכנס מספר או כינוי חדר";
"room_recents_join_room_title" = "הצטרף לחדר";
"room_recents_join_room" = "הצטרף לחדר";
"room_recents_create_empty_room" = "צור חדר";
"room_recents_start_chat_with" = "התחל צ'אט";
"room_recents_suggested_rooms_section" = "חדרים מומלצים";
"room_recents_invites_section" = "הזמנות";
"room_recents_server_notice_section" = "התראות מערכת";
"room_recents_low_priority_section" = "הרשאה נמוכה";
"room_recents_no_conversation" = "אין חדרים";
"room_recents_conversations_section" = "חדרים";
"room_recents_people_section" = "אנשים";
"room_recents_favourites_section" = "מועדפים";
// Room recents
"room_recents_directory_section" = "ספריית חדר";
"room_creation_dm_error" = "לא הצלחנו ליצור את מנהל ההתקן. אנא בדוק את המשתמשים אותם אתה מעוניין להזמין ונסה שוב.";
"social_login_list_title_sign_up" = "או";
"social_login_list_title_sign_in" = "או";
// Social login
"social_login_list_title_continue" = "המשך עם";
"auth_softlogout_clear_data_sign_out_msg" = "האם אתה בטוח שברצונך למחוק את כל המידע השמור כרגע במכשיר זה? התחבר שוב על מנת לגשת לחשבון המידע וההודעות שלך.";
"auth_softlogout_clear_data_message_2" = "מחק אותו שם סיימת להשתמש במכשיר זה, או אם אתה מעוניין להתחבר לחשבון אחר.";
"auth_softlogout_clear_data_message_1" = "זהירות:המידע האישי שלך (כולל מפתחות הצפנה) עדיין שמור במכשיר זה.";
"auth_softlogout_recover_encryption_keys" = "התחבר על מנת לשחזר את מפתחות ההצפנה השמור בצורה בלעדית במכשיר זה. תצטרך אותם על מנת לקרא את כל ההודעות המאובטחות שלך בכל מכשיר.";
"auth_softlogout_reason" = "מנהל שרת הבית שלך (% 1 $@) הוציא אותך החוצה מהחשבון שלך % 2 $@ (% 3 $@).";
"auth_autodiscover_invalid_response" = "תגובת חשיפה לא חוקית של שרת הבית";
"auth_accept_policies" = "אנא עבור ואשר את המדיניות בשרת הבית הזה:";
"auth_add_email_and_phone_warning" = "רישום באמצעות מייל ומספר טלפון יחד לא נתמך עדיין עד שה API קיים. רק מספר הטלפון יילקח בחשבון. תוכל להוסיף את כתובת המייל שלך בהגדרות הפרופיל שלך.";
"auth_reset_password_error_is_required" = "לא הוגדר שרת זיהוי: הוסף אחד באפשרויות שרת על מנת לאפס את סיסמת חשבון המטריצה שלך.";
"auth_reset_password_error_not_found" = "כתובת המייל שלך לא מופיעה כמשוייכת למספר המטריצה בשרת הבית הזה.";
"auth_reset_password_email_validation_message" = "אי מייל נשלח אל %@. ברגע שתעקוב הלינק המצורף, הקלק עליו.";
"auth_reset_password_missing_email" = "חובה להכניס את כתובת המייל המשוייכת לחשבון שלך.";
"auth_reset_password_message" = "על מנת לאפס את סיסמת החשבון שלך, הכנס את כתובת המייל המשוייכת לחשבון שלך:";
"auth_msisdn_validation_title" = "ממתין לאימות";
"auth_email_validation_message" = "אנא בדוק את כתובת המייל ע\"מ להמשיך ברישום";
"auth_use_server_options" = "השתמש באפשרות שרת מותאמות אישית (מתקדמת)";
"auth_email_not_found" = "שליחת הודעה נכשלה:כתובת מייל זו לא נמצאה";
"auth_forgot_password_error_no_configured_identity_server" = "לא הוגדר שרת זיהוי:הוסף אחד על מנת לאפס את סיסמת החשבון שלך.";
"auth_forgot_password" = "שכחת את סיסמת החשבון?";
"auth_untrusted_id_server" = "שרת הזיהוי אינו מהימן";
"auth_phone_is_required" = "לא הוגדר שרת זיהוי לכן אתה לא יכול להוסיף מספר טלפון על מנת לאפס את סיסמת החשבון שלך בעתיד.";
"auth_email_is_required" = "לא הוגדר שרת זיהוי לכן אתה לא יכול להוסיף כתובת מייל על מנת לאפס את סיסמת החשבון שלך בעתיד.";
"auth_add_email_phone_message_2" = "הגדר כתובת מייל עבור שחזור חשבון. השתמש במייל עדכני או טלפון על מנת להיות מזוהה ע\"י אנשים שמכירים אותך.";
"auth_identity_server_placeholder" = "דף (https://vector.im לדוגמא)";
"auth_repeat_new_password_placeholder" = "אשר את סיסמת החשבון החדשה שלך";
"onboarding_use_case_message" = "אנחנו נעזור לך להתחבר.";
"onboarding_use_case_title" = "עם מי תהיה לרוב בצ'אט?";
// Accessibility
"accessibility_checkbox_label" = "תיבת סימון";
"store_promotional_text" = "צ'אט הגנה על פרטיות ואפליקציית שיתוף, ברשת פתוחה. מבוזרים על מנת לשמור אותך בבקרה. ללא כריית מידע, דלתות אחוריות או גישה של צד שלישי.";
"sign_up" = "הירשם";
"dismiss" = "לדחות";
// Banner
"secure_backup_setup_banner_title" = "גיבוי מאובטח";
"secure_key_backup_setup_cancel_alert_message" = "אם תבטל כעת, אתה עלול לאבד הודעות מוצפנות ומידע אם תאבד את הגישה שלך.\n\nאתה יכול להגדיר גם גיבוי מאובטח ולנהל את המפתחות שלך בהגדרות.";
// Cancel
"secure_key_backup_setup_cancel_alert_title" = "האם אתה בטוח?";
"secure_key_backup_setup_existing_backup_error_delete_it" = "מחק זאת";
"secure_key_backup_setup_existing_backup_error_unlock_it" = "פתח נעילה זו";
"secure_key_backup_setup_existing_backup_error_info" = "בטל נעילה זו על מנת להשתמש בזה בגיבוי מאובטח או מחק זאת על מנת ליצור גיבוי הודעות חדש בגיבוי המאובטח.";
"secure_key_backup_setup_existing_backup_error_title" = "גיבוי להודעות כבר קיים";
"secure_key_backup_setup_intro_use_security_passphrase_info" = "השתמש בביטוי סודי שרק אתה יודע, וחולל מפתח לגיבוי.";
"secure_key_backup_setup_intro_use_security_passphrase_title" = "השתמש בביטוי מאובטח";
"secure_key_backup_setup_intro_use_security_key_info" = "חולל מפתח הצפנה כדי לשמור במקום בטוח כגון מנהל הצפנה או כספת.";
"secure_key_backup_setup_intro_use_security_key_title" = "השתמש במפתח אבטחה";
"secure_key_backup_setup_intro_info" = "אמצעי אבטחה למניעת איבוד גישה של הודעות מוצפנות ומידע באמצעות גיבוי מפתחות הצפנה בשרת שלך.";
// MARK: Secure backup setup
// Intro
"secure_key_backup_setup_intro_title" = "גיבוי מאובטח";
"rerequest_keys_alert_message" = "שלח בבקשה את %@ בהתקן אחר שיכול לפענח את ההודעה כך שניתן יהיה לשלוח את המפתח בממשק זה.";
// Re-request confirmation dialog
"rerequest_keys_alert_title" = "בקשה נשלחה";
"room_event_failed_to_send" = "שליחה נכשלה";
"room_warning_about_encryption" = "הצפנה קצה לקצה בפיתוח ולא ניתן לסמוך על כך.\n\nאתה עדיין לא יכול לסמוך על זה בשמירה על מידע.\n\nהתקנים עדיין לא יוכלו לפענח היסטוריה מלפני השלב בו הצטרפו לחדר.\n\nהודעות מוצפנות לא יוכלו להיות נצפות ע\"י משתמשים שטרם התקינו הצפנה.";
"room_event_copy_link_info" = "לינק הועתק ללוח הכתיבה.";
"settings_integrations_allow_button" = "נהל שילובים";
"settings_calls_stun_server_fallback_description" = "אפשר שרת סיוע לשיחות חלופיות %@ כאשר שרת הבית שלך לא מציע כזו (כתובת ה IP שלך תשותף במהלך השיחה).";
"settings_calls_stun_server_fallback_button" = "אפשר שרת סיוע לשיחות חלופיות";
"settings_callkit_info" = "קבל שיחות נכנסות במסך הנעילה שלך. ראה את %@ השיחות שלך בהיסטורית השיחות של המערכת. אם iCloud מאופשר, היסטורית שיחות אלה תשותף עם Apple.";
"settings_enable_callkit" = "שיחות משולבות";
"settings_mentions_and_keywords_encryption_notice" = "לא תקבל הודעות עבור תזכורות ומפתחות מלים בחדרים מוצפנים בסלולרי.";
"settings_new_keyword" = "הוסף מלת מפתח חדשה";
"settings_your_keywords" = "מפתח המלים שלך";
"key_backup_recover_done_action" = "בוצע";
// Success
"key_backup_recover_success_info" = "גיבוי שוחזר!";
"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "אבדת את מפתח האבטחה שלך אתה יכול להגדיר חדש בתפריט הגדרות.";
"key_backup_recover_from_recovery_key_recover_action" = "פתח הסטוריה מנעילה";
"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "הכנס מפתח אבטחה";
"key_backup_recover_from_recovery_key_recovery_key_title" = "הכנס";
// Recover from recovery key
"key_backup_recover_from_recovery_key_info" = "השתמש במפתח האבטחה שלך על מנת לפתוח את הסטורית ההודעות שלך";
"key_backup_recover_from_passphrase_lost_passphrase_action_part3" = ".";
"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "השתמש במפתח האבטחה שלך";
"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "לא יודע את את ביטוי האבטחה שלך? אתה יכול ";
"key_backup_recover_from_passphrase_recover_action" = "בטל נעילת היסטוריה";
"key_backup_recover_from_passphrase_passphrase_placeholder" = "הכנס ביטוי";
"key_backup_recover_from_passphrase_passphrase_title" = "הכנס";
// Recover from passphrase
"key_backup_recover_from_passphrase_info" = "השתמש בביטוי האבטחה שלך על מנת לפתוח הסטורית הודעות מאובטחת";
// Recover from private key
"key_backup_recover_from_private_key_info" = "משחזר גיבוי…";
"key_backup_recover_invalid_recovery_key" = "הגיבוי לא יכול להיות מפוענח באמצעות מפתח זה: אנא וודא שהכנסת את מפתח האבטחה הנכון.";
"key_backup_recover_invalid_recovery_key_title" = "מפתח אבטחה לא תואם";
"key_backup_recover_invalid_passphrase" = "הגיבוי לא יכול להיות מפוענח עם ביטוי זה: אנא וודא שהכנסת את ביטוי האבטחה הנכון.";
"key_backup_recover_invalid_passphrase_title" = "ביטוי אבטחה לא תקין";
// MARK: Key backup recover
"key_backup_recover_title" = "הודעות מאובטחות";
// Success from secure backup
"key_backup_setup_success_from_secure_backup_info" = "המפתחות שלך מגובים.";
"key_backup_setup_success_from_recovery_key_made_copy_action" = "ביצעתי עותק";
"key_backup_setup_success_from_recovery_key_make_copy_action" = "בצע עותק";
"key_backup_setup_success_from_recovery_key_recovery_key_title" = "מפתח אבטחה";
// Success from recovery key
"key_backup_setup_success_from_recovery_key_info" = "המפתחות שלך גובו.\n\nהעתק את מפתח ההצפנה ושמור אותו בטוח.";
"key_backup_setup_success_from_passphrase_done_action" = "בוצע";
"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "שמור מפתח אבטחה";
// Success from passphrase
"key_backup_setup_success_from_passphrase_info" = "המפתחות שלך גובו.\n\nמפתח האבטחה שלך הוא רשת ביטחון - אתה יכול לעשות בו שימוש לשחזור הגישה להודעות המוצפנות שלך אם שכחת את ביטוי הסיסמה שלך.\n\nשמור את מפתח האבטחה שלך במקום מאובטח ביותר, כגון מנהל סיסמאות (או כספת).";
// Success
"key_backup_setup_success_title" = "הצלחה!";
"key_backup_setup_passphrase_setup_recovery_key_action" = "(מתקדם) הגדרה עם מפתח אבטחה";
"key_backup_setup_passphrase_setup_recovery_key_info" = "או, אבטח את הגיבוי שלך עם מפתח אבטחה, שמור אותו במקום בטוח.";
"key_backup_setup_passphrase_set_passphrase_action" = "בחר ביטוי";
"key_backup_setup_passphrase_confirm_passphrase_invalid" = "ביטוי לא תואם";
"key_backup_setup_passphrase_confirm_passphrase_valid" = "מעולה!";
"key_backup_setup_passphrase_confirm_passphrase_placeholder" = "אשר ביטוי";
"key_backup_setup_passphrase_confirm_passphrase_title" = "אשר";
"key_backup_setup_passphrase_passphrase_invalid" = "נסה להוסיף מלה";
"key_backup_setup_passphrase_passphrase_valid" = "מעולה!";
"key_backup_setup_passphrase_passphrase_placeholder" = "הכנס ביטוי";
"key_backup_setup_passphrase_passphrase_title" = "הכנס";
"key_backup_setup_passphrase_info" = "אנחנו נשמור גיבוי מוצפן של המפתחות שלך בשרת שלנו. הגן על הגיבוי שלך באמצעות ביטוי על מנת להשאיר אותו בטוח.\n\nלאבטחה מקסימלית, זה אמור להיות שונה מסיסמת החשבון שלך.";
// Passphrase
"key_backup_setup_passphrase_title" = "אבטח את הגיבוי שלך באמצעות ביטוי מאובטח";
"key_backup_setup_intro_manual_export_action" = "הוצא מפתחות בצורה ידנית";
"key_backup_setup_intro_manual_export_info" = "(מתקדם)";
"key_backup_setup_intro_setup_connect_action_with_existing_backup" = "חבר התקן זה לגיבוי מפתח";
"key_backup_setup_intro_setup_action_without_existing_backup" = "התחל להשתמש בגיבוי מפתח";
"key_backup_setup_intro_info" = "הודעות בחדרים מוצפנים הן מאובטחות קצה לקצה. רק לך ולמקבל(ים) יש את המפתחות לקרוא הודעות אלה.\n\nגבה בצורה מאובטחת את המפתחות שלך על מנת להימנע מלאבד אותם.";
// Intro
"key_backup_setup_intro_title" = "אף פעם אל תאבד הודעות מוצפנות";
"key_backup_setup_skip_alert_skip_action" = "דלג";
"key_backup_setup_skip_alert_message" = "אתה עלול לאבד הודעות מאובטחות אם תתנתק או תאבד את המכשיר שלך.";
"key_backup_setup_skip_alert_title" = "האם אתה בטוח?";
// MARK: Key backup setup
"key_backup_setup_title" = "גיבוי מפתח";
"secure_backup_setup_banner_subtitle" = "אמצעי אבטחה למניעת איבוד גישה להודעות מוצפנות ומידע";
"settings_confirm_media_size" = "אשר גודל בזמן שליחה";
"settings_three_pids_management_information_part3" = ".";
"settings_three_pids_management_information_part2" = "גלוי";
"settings_three_pids_management_information_part1" = "קבע איזה כתובות אימייל או מספרי טלפון אתה יכול לעשות בהם שימוש להתחברות או לשחזור החשבון שלך כאן. החלט מי יכול למצא אותך ב ";
"settings_fail_to_update_profile" = "כשלון בעדכון הפרופיל";
"settings_night_mode" = "מצב לילה";
"settings_change_password" = "שנה סיסמת חשבון מטריקס";
"settings_add_phone_number" = "הוסף מספר טלפון";
"settings_phone_number" = "טלפון";
"settings_add_email_address" = "הוסף כתובת אימייל";
"settings_email_address_placeholder" = "הכנס את כתובת האימייל שלך";
"settings_email_address" = "אימייל";
"settings_display_name" = "הצג שם";
"settings_profile_picture" = "תמונת פרופיל";
"settings_sign_out_e2e_warn" = "אתה תאבד את מפתחות ההצפנה קצה לקצה. המשמעות היא שלא תוכל יותר לקרא הודעות ישנות בחדרים מוצפנים בהתקן זה.";
"settings_sign_out_confirmation" = "האם אתה בטוח?";
"settings_ui_theme_light" = "אור";
"settings_ui_theme_auto" = "אוטומטי";
"settings_ui_theme" = "נושא";
"settings_ui_language" = "שפה";
"settings_integrations_allow_description" = "השתמש במנהל שילובים (%@) לניהול בוטים, גשרי ועידה, וו'ידגטים ומדבקות.\n\nמנהלי שילובים מקבלים נתוני תצורה, ויכולים להגדיר וו'ידגטים, לשלוח הזמנות חדרים ורמות הספק בשמך.";

View file

@ -2136,3 +2136,5 @@
"home_syncing" = "Szinkronizálás";
"room_participants_leave_success" = "Szobából kilépve";
"room_participants_leave_processing" = "Távozás";
"notice_error_unformattable_event" = "** Az üzenetet nem lehet megjeleníteni. Kérlek jelezd ezt a hibát";
"settings_labs_use_only_latest_user_avatar_and_name" = "A felhasználó jelenlegi profilképének és nevének megjelenítése a régi üzeneteknél is";

View file

@ -298,7 +298,7 @@
"emoji_picker_flags_category" = "Bendera";
"emoji_picker_symbols_category" = "Simbol";
"emoji_picker_objects_category" = "Benda";
"emoji_picker_activity_category" = "Aktifitas";
"emoji_picker_activity_category" = "Aktivitas";
// MARK: Emoji picker
"emoji_picker_title" = "Reaksi";
@ -1859,7 +1859,7 @@
"login_error_resource_limit_exceeded_title" = "Melebihi Batas Sumber";
"login_desktop_device" = "Desktop";
"login_tablet_device" = "Tablet";
"login_mobile_device" = "Mobile";
"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";
@ -1947,7 +1947,7 @@
"notification_settings_per_sender_notifications" = "Notifikasi per pengirim";
"notification_settings_per_room_notifications" = "Notifikasi per ruangan";
"notification_settings_custom_sound" = "Suara kustom";
"notification_settings_highlight" = "Highlight";
"notification_settings_highlight" = "Sorotan";
"notification_settings_word_to_match" = "kata untuk dicocokkan";
"notification_settings_never_notify" = "Jangan diberitahu";
"notification_settings_always_notify" = "Selalu diberitahu";
@ -2333,3 +2333,5 @@
"home_syncing" = "Menyinkronkan";
"room_participants_leave_success" = "Telah keluar dari ruangan";
"room_participants_leave_processing" = "Meninggalkan";
"notice_error_unformattable_event" = "** Tidak dapat memuat pesan. Mohon laporkan sebuah kutu";
"settings_labs_use_only_latest_user_avatar_and_name" = "Tampilkan avatar dan nama terkini untuk pengguna di riwayat pesan";

View file

@ -1 +1,10 @@
"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.";
"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ð.";
// Permissions usage explanations
"NSCameraUsageDescription" = "Myndavélin er notuð til að taka myndir og myndskeið og fyrir myndsímtöl.";

View file

@ -1 +1,170 @@
/** Media Messages **/
/* New image message from a specific person, not referencing a room. */
"PICTURE_FROM_USER" = "%@ sendi mynd";
/** Key verification **/
"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ vill sannreyna";
/* Group call from user, CallKit caller name */
"GROUP_CALL_FROM_USER" = "%@ (hópsímtal)";
/* A user added a Jitsi call to a room */
"GROUP_CALL_STARTED" = "Hópsímtal er byrjað";
/* Incoming named video conference invite from a specific person */
"VIDEO_CONF_NAMED_FROM_USER" = "Myndsamtal í hópi frá %@: '%@'";
/* Incoming named voice conference invite from a specific person */
"VOICE_CONF_NAMED_FROM_USER" = "Hópsímtal frá %@: '%@'";
/* Incoming unnamed video conference invite from a specific person */
"VIDEO_CONF_FROM_USER" = "Myndsamtal í hópi frá %@";
/* Incoming unnamed voice conference invite from a specific person */
"VOICE_CONF_FROM_USER" = "Hópsímtal frá %@";
/* Incoming one-to-one video call */
"VIDEO_CALL_FROM_USER" = "Myndsamtal frá %@";
/** Calls **/
/* Incoming one-to-one voice call */
"VOICE_CALL_FROM_USER" = "Símtal frá %@";
/* A user's membership has updated in an unknown way */
"USER_MEMBERSHIP_UPDATED" = "%@ uppfærði notandasniðið sitt";
/* A user has change their avatar */
"USER_UPDATED_AVATAR" = "%@ breytti auðkennismynd sinni";
/* A user has change their name to a new name which we don't know */
"GENERIC_USER_UPDATED_DISPLAYNAME" = "%@ breytti nafni sínu";
/** Membership Updates **/
/* A user has change their name to a new name */
"USER_UPDATED_DISPLAYNAME" = "%@ breyttu nafni sínu í %@";
/* A user has invited you to a named room */
"USER_INVITE_TO_NAMED_ROOM" = "%@ bauð þér í %@";
/* A user has invited you to an (unamed) group chat */
"USER_INVITE_TO_CHAT_GROUP_CHAT" = "%@ bauð þér að taka þátt í hópspjalli";
/** Invites **/
/* A user has invited you to a chat */
"USER_INVITE_TO_CHAT" = "%@ hefur boðið þér að spjalla";
/* A user has reacted to a message, but the reaction content is unknown */
"GENERIC_REACTION_FROM_USER" = "%@ sendi viðbrögð";
/** Reactions **/
/* A user has reacted to a message, including the reaction e.g. "Alice reacted 👍". */
"REACTION_FROM_USER" = "%@ brást við með %@";
/* Look, stuff's happened, alright? Just open the app. */
"MSGS_IN_TWO_PLUS_ROOMS" = "@ ný skilaboð í %@, %@ og víðar";
/* Multiple messages in two rooms */
"MSGS_IN_TWO_ROOMS" = "%@ ný skilaboð í %@ og %@";
/* Multiple unread messages from two plus people (ie. for 4+ people: 'others' replaces the third person) */
"MSGS_FROM_TWO_PLUS_USERS" = "%@ ný skilaboð frá %@, %@ og fleirum";
/* Multiple unread messages from three people */
"MSGS_FROM_THREE_USERS" = "%@ ný skilaboð frá %@, %@ og %@";
/* Multiple unread messages from two people */
"MSGS_FROM_TWO_USERS" = "%@ ný skilaboð frá %@ og %@";
/* Multiple unread messages from a specific person, not referencing a room */
"MSGS_FROM_USER" = "%@ ný skilaboð í %@";
/** Coalesced messages **/
/* Multiple unread messages in a room */
"UNREAD_IN_ROOM" = "%@ ný skilaboð í %@";
/* New message with hidden content due to PIN enabled */
"MESSAGE_PROTECTED" = "Ný skilaboð";
/* New message indicator on a room */
"MESSAGE_IN_X" = "Skilaboð í %@";
/* New message indicator from a DM */
"MESSAGE_FROM_X" = "Skilaboð frá %@";
/** Notification messages **/
/* New message indicator on unknown room */
"MESSAGE" = "Skilaboð";
/* Sticker from a specific person, not referencing a room. */
"STICKER_FROM_USER" = "%@ sendi límmerki";
/* A single unread message */
"SINGLE_UNREAD" = "Þú hefur fengið skilaboð";
/* A single unread message in a room */
"SINGLE_UNREAD_IN_ROOM" = "Þú hefur fengið skilaboð í %@";
/* New file message from a specific person, not referencing a room. */
"LOCATION_FROM_USER" = "%@ hefur deilt staðsetningu sinni";
/* New file message from a specific person, not referencing a room. */
"FILE_FROM_USER" = "%@ sendi skrá %@";
/* New voice message from a specific person, not referencing a room. */
"VOICE_MESSAGE_FROM_USER" = "%@ sendi talskilaboð";
/* New audio message from a specific person, not referencing a room. */
"AUDIO_FROM_USER" = "%@ sendi hljóðskrá %@";
/* New video message from a specific person, not referencing a room. */
"VIDEO_FROM_USER" = "%@ sendi myndskeið";
/* New image message from a specific person in a named room. */
"IMAGE_FROM_USER_IN_ROOM" = "%@ birti mynd %@ í %@";
/* New action message from a specific person in a named room. */
"ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@";
/* New action message from a specific person, not referencing a room. */
"ACTION_FROM_USER" = "* %@ %@";
/* New message from a specific person in a named room. Content included. */
"MSG_FROM_USER_IN_ROOM_WITH_CONTENT" = "%@ í %@: %@";
/** Single, unencrypted messages (where we can include the content */
/* New message from a specific person, not referencing a room. Content included. */
"MSG_FROM_USER_WITH_CONTENT" = "%@: %@";
/* New message from a specific person in a named room */
"MSG_FROM_USER_IN_ROOM" = "%@ birti í %@";
/** Single, end-to-end encrypted messages (ie. we don't know what they say) */
/* New message from a specific person, not referencing a room */
"MSG_FROM_USER" = "%@ sendi skilboð";
/* New message reply from a specific person in a named room. */
"REPLY_FROM_USER_IN_ROOM_TITLE" = "%@ svaraði í %@";
/* New message reply from a specific person, not referencing a room. */
"REPLY_FROM_USER_TITLE" = "%@ svaraði";
/** Titles **/
/* Message title for a specific person in a named room */
"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ í %@";
/** General **/
"NOTIFICATION" = "Tilkynning";

File diff suppressed because it is too large Load diff

View file

@ -2109,3 +2109,5 @@
"home_syncing" = "Sincronizzazione";
"room_participants_leave_success" = "Stanza abbandonata";
"room_participants_leave_processing" = "Uscita in corso";
"notice_error_unformattable_event" = "** Impossibile visualizzare il messaggio. Si prega di segnalare l'errore";
"settings_labs_use_only_latest_user_avatar_and_name" = "Mostra avatar e nome più recenti per gli utenti nella cronologia dei messaggi";

View file

@ -1477,7 +1477,7 @@
"notification_settings_always_notify" = "常に通知";
"notification_settings_never_notify" = "決して通知しない";
"notification_settings_word_to_match" = "一致する単語";
"notification_settings_highlight" = "Highlight";
"notification_settings_highlight" = "ハイライト";
"notification_settings_custom_sound" = "カスタムサウンド";
"notification_settings_per_room_notifications" = "1ルームあたりの通知";
"notification_settings_per_sender_notifications" = "送信者ごとの通知";
@ -1600,3 +1600,17 @@
"settings_sending_media" = "画像と動画の送信";
"invite_friends_share_text" = "%@ での連絡先: %@";
"side_menu_action_invite_friends" = "招待する";
"call_more_actions_change_audio_device" = "オーディオデバイスを変更";
"call_more_actions_dialpad" = "ダイヤルパッド";
"onboarding_splash_login_button_title" = "既にアカウントを持っています";
// Onboarding
"onboarding_splash_register_button_title" = "アカウントを作成";
"notice_room_created_by_you_for_dm" = "参加しました";
"notice_room_created_for_dm" = "%@が参加しました";
"onboarding_use_case_existing_server_button" = "サーバーに接続";
"callbar_only_single_active_group" = "タップしてグループ通話に参加 (%@)";
"settings_confirm_media_size" = "送信時のサイズ確認";
"settings_confirm_media_size_description" = "この機能をオンにすると、画像や動画をどのサイズで送信するか確認する画面が表示されます。";
"settings_contacts_enable_sync_description" = "IDサーバーを使用して連絡先を探すと同時に、連絡先があなたを探せるようにします。";
"home_syncing" = "同期中";

View file

@ -2107,3 +2107,5 @@
"home_syncing" = "Sincando";
"room_participants_leave_success" = "Saiu de sala";
"room_participants_leave_processing" = "Saindo";
"notice_error_unformattable_event" = "** Incapaz de render mensagem. Por favor reporte um bug";
"settings_labs_use_only_latest_user_avatar_and_name" = "Mostrar avatar e nomes mais recentes para usuárias(os) em histórico de mensagem";

View file

@ -490,7 +490,7 @@
"unknown_devices_verify" = "Overiť…";
"media_type_accessibility_sticker" = "Nálepka";
"media_type_accessibility_file" = "Súbor";
"media_type_accessibility_location" = "Miesto";
"media_type_accessibility_location" = "Poloha";
"media_type_accessibility_video" = "Video";
"media_type_accessibility_audio" = "Audio";
"media_type_accessibility_image" = "Obrázok";
@ -644,7 +644,7 @@
"room_details_low_priority_tag" = "Nízka priorita";
"room_details_room_name" = "Názov miestnosti";
"room_details_photo" = "Obrázok miestnosti";
"room_details_search" = "Hľadať miestnosť";
"room_details_search" = "Prehľadať miestnosť";
"identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "Napriek tomu sa odpojiť";
// Identity server settings
@ -1680,7 +1680,7 @@
"poll_edit_form_poll_type" = "Typ ankety";
"poll_edit_form_update_failure_title" = "Nepodarilo sa aktualizovať anketu";
"poll_edit_form_update_failure_subtitle" = "Prosím, skúste to znova";
"poll_edit_form_poll_type_open" = "Otvoriť anketu";
"poll_edit_form_poll_type_open" = "Otvorená anketa";
"poll_edit_form_poll_type_open_description" = "Hlasujúci uvidia výsledky hneď po hlasovaní";
"poll_edit_form_poll_type_closed_description" = "Výsledky sa zobrazia až po ukončení ankety";
"poll_timeline_total_votes_not_voted" = "%lu odovzdaných hlasov. Hlasujte a pozrite si výsledky";
@ -2320,3 +2320,13 @@
"notice_event_redacted_by_you" = " vami";
"home_syncing" = "Synchronizácia";
"room_participants_leave_success" = "Opustil miestnosť";
"notice_error_unformattable_event" = "** Správa sa nedá zobraziť. Prosím, nahláste chybu";
"settings_labs_use_only_latest_user_avatar_and_name" = "Zobraziť posledný obrázok a meno používateľov v histórii správ";
"room_participants_ago" = "pred";
"notice_location_attachment" = "umiestnenie prílohy";
"room_details_fail_to_update_room_direct" = "Nepodarilo sa aktualizovať priamy príznak tejto miestnosti";
"room_details_flair_section" = "Zobraziť štýl pre komunity";
"settings_flair" = "Zobraziť štýl, kde je to povolené";
"room_participants_leave_processing" = "Opustenie";
"joined" = "Sa pripojil/a";
"callbar_return" = "Späť";

View file

@ -2124,3 +2124,7 @@
"attachment_unsupported_preview_title" = "Sarrihet të bëhet paraparje";
"room_displayname_all_other_members_left" = "%@ (Iku)";
"message_reply_to_sender_sent_their_location" = "ka dhënë vendndodhjen e vet.";
"room_participants_leave_processing" = "Dalje";
"notice_error_unformattable_event" = "** Sarrihet të riprodhohet mesazhi. Ju lutemi, njoftoni një të metë";
"settings_labs_use_only_latest_user_avatar_and_name" = "Shfaq në historik mesazhesh avatarin dhe emrin më të ri të përdoruesve";
"room_participants_leave_success" = "Doli nga dhoma";

View file

@ -2068,3 +2068,8 @@
"attachment_unsupported_preview_title" = "Kunde inte förhandsgranska";
"room_displayname_all_other_members_left" = "%@ (Kvar)";
"message_reply_to_sender_sent_their_location" = "har delat sin plats.";
"notice_error_unformattable_event" = "** Kunde inte rendera meddelande. Vänligen rapportera en bugg";
"home_syncing" = "Synkar";
"settings_labs_use_only_latest_user_avatar_and_name" = "Visa senaste avatar och namn för användare i meddelandehistoriken";
"room_participants_leave_success" = "Lämnade rummet";
"room_participants_leave_processing" = "Lämnar";

View file

@ -2330,3 +2330,5 @@
"home_syncing" = "Синхронізація";
"room_participants_leave_success" = "Вихід успішний";
"room_participants_leave_processing" = "Вихід триває";
"notice_error_unformattable_event" = "** Неможливо показати повідомлення. Надішліть звіт про помилку";
"settings_labs_use_only_latest_user_avatar_and_name" = "Показувати останній аватар та ім'я для користувачів у історії повідомлень";

View file

@ -19,7 +19,7 @@ import Foundation
public extension Bundle {
/// Returns the real app bundle.
/// Can also be used in app extensions.
static var app: Bundle {
@objc static var app: Bundle {
let bundle = main
if bundle.bundleURL.pathExtension == "appex" {
// Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
@ -31,6 +31,14 @@ public extension Bundle {
return bundle
}
/// Get an lproj language bundle from the main app bundle.
/// - Parameter language: The language to try to load.
/// - Returns: The lproj bundle if found otherwise `nil`.
@objc static func lprojBundle(for language: String) -> Bundle? {
guard let lprojURL = Bundle.app.url(forResource: language, withExtension: "lproj") else { return nil }
return Bundle(url: lprojURL)
}
/// Whether or not the bundle is the RiotShareExtension.
var isShareExtension: Bool {
bundleURL.lastPathComponent.contains("RiotShareExtension.appex")

View file

@ -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
public extension NSAttributedString {
/// Returns a new attributed string by removing all links from the receiver.
@objc var vc_byRemovingLinks: NSAttributedString {
let result = NSMutableAttributedString(attributedString: self)
result.removeAttribute(.link, range: NSRange(location: 0, length: length))
return result
}
}

View file

@ -116,18 +116,18 @@ internal class Asset: NSObject {
internal static let cameraStop = ImageAsset(name: "camera_stop")
internal static let cameraVideoCapture = ImageAsset(name: "camera_video_capture")
internal static let videoIcon = ImageAsset(name: "video_icon")
internal static let onboardingSplashScreenPage1 = ImageAsset(name: "OnboardingSplashScreenPage1")
internal static let onboardingSplashScreenPage1Dark = ImageAsset(name: "OnboardingSplashScreenPage1Dark")
internal static let onboardingSplashScreenPage2 = ImageAsset(name: "OnboardingSplashScreenPage2")
internal static let onboardingSplashScreenPage2Dark = ImageAsset(name: "OnboardingSplashScreenPage2Dark")
internal static let onboardingSplashScreenPage3 = ImageAsset(name: "OnboardingSplashScreenPage3")
internal static let onboardingSplashScreenPage3Dark = ImageAsset(name: "OnboardingSplashScreenPage3Dark")
internal static let onboardingSplashScreenPage4 = ImageAsset(name: "OnboardingSplashScreenPage4")
internal static let onboardingSplashScreenPage4Dark = ImageAsset(name: "OnboardingSplashScreenPage4Dark")
internal static let onboardingAvatarCamera = ImageAsset(name: "onboarding_avatar_camera")
internal static let onboardingAvatarEdit = ImageAsset(name: "onboarding_avatar_edit")
internal static let onboardingCelebrationIcon = ImageAsset(name: "onboarding_celebration_icon")
internal static let onboardingCongratulationsIcon = ImageAsset(name: "onboarding_congratulations_icon")
internal static let onboardingSplashScreenPage1 = ImageAsset(name: "onboarding_splash_screen_page_1")
internal static let onboardingSplashScreenPage1Dark = ImageAsset(name: "onboarding_splash_screen_page_1_dark")
internal static let onboardingSplashScreenPage2 = ImageAsset(name: "onboarding_splash_screen_page_2")
internal static let onboardingSplashScreenPage2Dark = ImageAsset(name: "onboarding_splash_screen_page_2_dark")
internal static let onboardingSplashScreenPage3 = ImageAsset(name: "onboarding_splash_screen_page_3")
internal static let onboardingSplashScreenPage3Dark = ImageAsset(name: "onboarding_splash_screen_page_3_dark")
internal static let onboardingSplashScreenPage4 = ImageAsset(name: "onboarding_splash_screen_page_4")
internal static let onboardingSplashScreenPage4Dark = ImageAsset(name: "onboarding_splash_screen_page_4_dark")
internal static let onboardingUseCaseCommunity = ImageAsset(name: "onboarding_use_case_community")
internal static let onboardingUseCaseCommunityDark = ImageAsset(name: "onboarding_use_case_community_dark")
internal static let onboardingUseCaseIcon = ImageAsset(name: "onboarding_use_case_icon")
@ -173,6 +173,7 @@ internal class Asset: NSObject {
internal static let voiceCallHangonIcon = ImageAsset(name: "voice_call_hangon_icon")
internal static let voiceCallHangupIcon = ImageAsset(name: "voice_call_hangup_icon")
internal static let liveLocationIcon = ImageAsset(name: "live_location_icon")
internal static let locationCenterMapIcon = ImageAsset(name: "location_center_map_icon")
internal static let locationLiveIcon = ImageAsset(name: "location_live_icon")
internal static let locationMarkerIcon = ImageAsset(name: "location_marker_icon")
internal static let locationPinIcon = ImageAsset(name: "location_pin_icon")

View file

@ -162,6 +162,11 @@ internal enum StoryboardScene {
internal static let initialScene = InitialSceneType<Riot.ReactionHistoryViewController>(storyboard: ReactionHistoryViewController.self)
}
internal enum RoomContextPreviewViewController: StoryboardType {
internal static let storyboardName = "RoomContextPreviewViewController"
internal static let initialScene = InitialSceneType<Riot.RoomContextPreviewViewController>(storyboard: RoomContextPreviewViewController.self)
}
internal enum RoomContextualMenuViewController: StoryboardType {
internal static let storyboardName = "RoomContextualMenuViewController"
@ -279,11 +284,6 @@ internal enum StoryboardScene {
internal static let initialScene = InitialSceneType<Riot.SpaceMenuViewController>(storyboard: SpaceMenuViewController.self)
}
internal enum SpaceRoomPreviewViewController: StoryboardType {
internal static let storyboardName = "SpaceRoomPreviewViewController"
internal static let initialScene = InitialSceneType<Riot.SpaceRoomPreviewViewController>(storyboard: SpaceRoomPreviewViewController.self)
}
internal enum TemplateScreenViewController: StoryboardType {
internal static let storyboardName = "TemplateScreenViewController"
@ -294,6 +294,11 @@ internal enum StoryboardScene {
internal static let initialScene = InitialSceneType<Riot.ThreadListViewController>(storyboard: ThreadListViewController.self)
}
internal enum ThreadsBetaViewController: StoryboardType {
internal static let storyboardName = "ThreadsBetaViewController"
internal static let initialScene = InitialSceneType<Riot.ThreadsBetaViewController>(storyboard: ThreadsBetaViewController.self)
}
internal enum ThreadsNoticeViewController: StoryboardType {
internal static let storyboardName = "ThreadsNoticeViewController"

View file

@ -3775,6 +3775,74 @@ public class VectorL10n: NSObject {
public static var on: String {
return VectorL10n.tr("Vector", "on")
}
/// Profile picture
public static var onboardingAvatarAccessibilityLabel: String {
return VectorL10n.tr("Vector", "onboarding_avatar_accessibility_label")
}
/// You can change this anytime.
public static var onboardingAvatarMessage: String {
return VectorL10n.tr("Vector", "onboarding_avatar_message")
}
/// Add a profile picture
public static var onboardingAvatarTitle: String {
return VectorL10n.tr("Vector", "onboarding_avatar_title")
}
/// Let's go
public static var onboardingCelebrationButton: String {
return VectorL10n.tr("Vector", "onboarding_celebration_button")
}
/// Your preferences have been saved.
public static var onboardingCelebrationMessage: String {
return VectorL10n.tr("Vector", "onboarding_celebration_message")
}
/// Youre all set!
public static var onboardingCelebrationTitle: String {
return VectorL10n.tr("Vector", "onboarding_celebration_title")
}
/// Take me home
public static var onboardingCongratulationsHomeButton: String {
return VectorL10n.tr("Vector", "onboarding_congratulations_home_button")
}
/// Your account %@ has been created.
public static func onboardingCongratulationsMessage(_ p1: String) -> String {
return VectorL10n.tr("Vector", "onboarding_congratulations_message", p1)
}
/// Personalise profile
public static var onboardingCongratulationsPersonalizeButton: String {
return VectorL10n.tr("Vector", "onboarding_congratulations_personalize_button")
}
/// Congratulations!
public static var onboardingCongratulationsTitle: String {
return VectorL10n.tr("Vector", "onboarding_congratulations_title")
}
/// You can change this later
public static var onboardingDisplayNameHint: String {
return VectorL10n.tr("Vector", "onboarding_display_name_hint")
}
/// Your display name must be less than 256 characters
public static var onboardingDisplayNameMaxLength: String {
return VectorL10n.tr("Vector", "onboarding_display_name_max_length")
}
/// This will be shown when you send messages.
public static var onboardingDisplayNameMessage: String {
return VectorL10n.tr("Vector", "onboarding_display_name_message")
}
/// Display Name
public static var onboardingDisplayNamePlaceholder: String {
return VectorL10n.tr("Vector", "onboarding_display_name_placeholder")
}
/// Choose a display name
public static var onboardingDisplayNameTitle: String {
return VectorL10n.tr("Vector", "onboarding_display_name_title")
}
/// Save and continue
public static var onboardingPersonalizationSave: String {
return VectorL10n.tr("Vector", "onboarding_personalization_save")
}
/// Skip this step
public static var onboardingPersonalizationSkip: String {
return VectorL10n.tr("Vector", "onboarding_personalization_skip")
}
/// I already have an account
public static var onboardingSplashLoginButtonTitle: String {
return VectorL10n.tr("Vector", "onboarding_splash_login_button_title")
@ -6827,6 +6895,18 @@ public class VectorL10n: NSObject {
public static var settingsPinRoomsWithUnread: String {
return VectorL10n.tr("Vector", "settings_pin_rooms_with_unread")
}
/// Presence
public static var settingsPresence: String {
return VectorL10n.tr("Vector", "settings_presence")
}
/// Offline Mode
public static var settingsPresenceOfflineMode: String {
return VectorL10n.tr("Vector", "settings_presence_offline_mode")
}
/// If enabled, you will always appear offline to other users, even when using the application.
public static var settingsPresenceOfflineModeDescription: String {
return VectorL10n.tr("Vector", "settings_presence_offline_mode_description")
}
/// Privacy Policy
public static var settingsPrivacyPolicy: String {
return VectorL10n.tr("Vector", "settings_privacy_policy")
@ -7591,6 +7671,26 @@ public class VectorL10n: NSObject {
public static var threadsActionMyThreads: String {
return VectorL10n.tr("Vector", "threads_action_my_threads")
}
/// Not now
public static var threadsBetaCancel: String {
return VectorL10n.tr("Vector", "threads_beta_cancel")
}
/// Try it out
public static var threadsBetaEnable: String {
return VectorL10n.tr("Vector", "threads_beta_enable")
}
/// Keep discussions organised with threads.\n\nThreads help keep your conversations on-topic and easy to track.
public static var threadsBetaInformation: String {
return VectorL10n.tr("Vector", "threads_beta_information")
}
/// Learn more
public static var threadsBetaInformationLink: String {
return VectorL10n.tr("Vector", "threads_beta_information_link")
}
/// Threads
public static var threadsBetaTitle: String {
return VectorL10n.tr("Vector", "threads_beta_title")
}
/// Threads help keep your conversations on-topic and easy to track.
public static var threadsEmptyInfoAll: String {
return VectorL10n.tr("Vector", "threads_empty_info_all")
@ -7986,7 +8086,7 @@ public class VectorL10n: NSObject {
extension VectorL10n {
static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
let format = NSLocalizedString(key, tableName: table, bundle: Bundle(for: BundleToken.self), comment: "")
let format = NSLocalizedString(key, tableName: table, bundle: Bundle.app, comment: "")
let locale: Locale
if let providedLocale = LocaleProvider.locale {
locale = providedLocale
@ -7998,4 +8098,3 @@ extension VectorL10n {
}
}
private final class BundleToken {}

View file

@ -14,74 +14,6 @@ public extension VectorL10n {
static var imagePickerActionFiles: String {
return VectorL10n.tr("Untranslated", "image_picker_action_files")
}
/// Profile picture
static var onboardingAvatarAccessibilityLabel: String {
return VectorL10n.tr("Untranslated", "onboarding_avatar_accessibility_label")
}
/// You can change this anytime.
static var onboardingAvatarMessage: String {
return VectorL10n.tr("Untranslated", "onboarding_avatar_message")
}
/// Add a profile picture
static var onboardingAvatarTitle: String {
return VectorL10n.tr("Untranslated", "onboarding_avatar_title")
}
/// Let's go
static var onboardingCelebrationButton: String {
return VectorL10n.tr("Untranslated", "onboarding_celebration_button")
}
/// Your preferences have been saved.
static var onboardingCelebrationMessage: String {
return VectorL10n.tr("Untranslated", "onboarding_celebration_message")
}
/// Youre all set!
static var onboardingCelebrationTitle: String {
return VectorL10n.tr("Untranslated", "onboarding_celebration_title")
}
/// Take me home
static var onboardingCongratulationsHomeButton: String {
return VectorL10n.tr("Untranslated", "onboarding_congratulations_home_button")
}
/// Your account %@ has been created.
static func onboardingCongratulationsMessage(_ p1: String) -> String {
return VectorL10n.tr("Untranslated", "onboarding_congratulations_message", p1)
}
/// Personalise profile
static var onboardingCongratulationsPersonalizeButton: String {
return VectorL10n.tr("Untranslated", "onboarding_congratulations_personalize_button")
}
/// Congratulations!
static var onboardingCongratulationsTitle: String {
return VectorL10n.tr("Untranslated", "onboarding_congratulations_title")
}
/// You can change this later
static var onboardingDisplayNameHint: String {
return VectorL10n.tr("Untranslated", "onboarding_display_name_hint")
}
/// Your display name must be less than 256 characters
static var onboardingDisplayNameMaxLength: String {
return VectorL10n.tr("Untranslated", "onboarding_display_name_max_length")
}
/// This will be shown when you send messages.
static var onboardingDisplayNameMessage: String {
return VectorL10n.tr("Untranslated", "onboarding_display_name_message")
}
/// Display Name
static var onboardingDisplayNamePlaceholder: String {
return VectorL10n.tr("Untranslated", "onboarding_display_name_placeholder")
}
/// Choose a display name
static var onboardingDisplayNameTitle: String {
return VectorL10n.tr("Untranslated", "onboarding_display_name_title")
}
/// Save and continue
static var onboardingPersonalizationSave: String {
return VectorL10n.tr("Untranslated", "onboarding_personalization_save")
}
/// Skip this step
static var onboardingPersonalizationSkip: String {
return VectorL10n.tr("Untranslated", "onboarding_personalization_skip")
}
/// This feature isn't available here. For now, you can do this with %@ on your computer.
static func spacesFeatureNotAvailable(_ p1: String) -> String {
return VectorL10n.tr("Untranslated", "spaces_feature_not_available", p1)

View file

@ -1214,25 +1214,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
[self.identityServerTextField resignFirstResponder];
// Report server url typed by the user as custom url.
NSString *homeServerURL = self.homeServerTextField.text;
if (homeServerURL.length && ![homeServerURL isEqualToString:self.defaultHomeServerUrl])
{
[[NSUserDefaults standardUserDefaults] setObject:homeServerURL forKey:@"customHomeServerURL"];
}
else
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"customHomeServerURL"];
}
NSString *identityServerURL = self.identityServerTextField.text;
if (identityServerURL.length && ![identityServerURL isEqualToString:self.defaultIdentityServerUrl])
{
[[NSUserDefaults standardUserDefaults] setObject:identityServerURL forKey:@"customIdentityServerURL"];
}
else
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"customIdentityServerURL"];
}
[self saveCustomServerInputs];
// Restore default configuration
[self setHomeServerTextFieldText:self.defaultHomeServerUrl];
@ -1275,6 +1257,29 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
}
}
- (void)saveCustomServerInputs
{
NSString *homeServerURL = self.homeServerTextField.text;
if (homeServerURL.length && ![homeServerURL isEqualToString:self.defaultHomeServerUrl])
{
[[NSUserDefaults standardUserDefaults] setObject:homeServerURL forKey:@"customHomeServerURL"];
}
else
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"customHomeServerURL"];
}
NSString *identityServerURL = self.identityServerTextField.text;
if (identityServerURL.length && ![identityServerURL isEqualToString:self.defaultIdentityServerUrl])
{
[[NSUserDefaults standardUserDefaults] setObject:identityServerURL forKey:@"customIdentityServerURL"];
}
else
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"customIdentityServerURL"];
}
}
- (void)showResourceLimitExceededError:(NSDictionary *)errorDict
{
MXLogDebug(@"[AuthenticationVC] showResourceLimitExceededError");
@ -1325,8 +1330,11 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
self.userInteractionEnabled = NO;
[self.authenticationActivityIndicator startAnimating];
// Hide the custom server details in order to save customized inputs
[self setCustomServerFieldsVisible:NO];
// Save customized server inputs if used
if (!self.customServersContainer.isHidden)
{
[self saveCustomServerInputs];
}
MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:userId];
MXSession *session = account.mxSession;

View file

@ -0,0 +1,98 @@
//
// 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
/// `PresenceIndicatorView` is used to display a presence indicator over an avatar.
@objcMembers
@IBDesignable
final class PresenceIndicatorView: UIView {
// MARK: - Internal Properties
@IBInspectable var borderWidth: CGFloat = 0.0
var borderColor: UIColor = ThemeService.shared().theme.backgroundColor
// MARK: - Private Properties
private let borderLayer = CALayer()
// MARK: - Private Constants
private enum Constants {
static let borderLayerOffset: CGFloat = 1.0
}
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
// MARK: - Override
override func layoutSubviews() {
super.layoutSubviews()
// This sets up a slightly larger border layer to avoid common iOS
// issue of having a very thin but noticeable additional border of
// backgroundColor when using corner radius + borderWidth.
self.layer.cornerRadius = self.frame.width / 2.0
self.borderLayer.borderWidth = self.borderWidth + Constants.borderLayerOffset
self.borderLayer.cornerRadius = self.layer.cornerRadius + Constants.borderLayerOffset
self.borderLayer.frame = self.frame.withOffset(Constants.borderLayerOffset)
}
// MARK: - Internal Methods
/// Updates presence indicator with given `MXPresence`.
///
/// - Parameters:
/// - presence: `MXPresence` to display
func setPresence(_ presence: MXPresence) {
switch presence {
case .online:
self.backgroundColor = ThemeService.shared().theme.tintColor
self.borderLayer.borderColor = self.borderColor.cgColor
case .offline, .unavailable:
self.backgroundColor = ThemeService.shared().theme.tabBarUnselectedItemTintColor
self.borderLayer.borderColor = self.borderColor.cgColor
default:
self.backgroundColor = UIColor.clear
self.borderLayer.borderColor = UIColor.clear.cgColor
}
}
}
// MARK: - Private Methods
private extension PresenceIndicatorView {
func setup() {
self.layer.addSublayer(borderLayer)
}
}
// MARK: - CGRect Helper
private extension CGRect {
/// Returns a `CGRect` with given offset on each side.
///
/// - Parameters:
/// - offset: offset to apply
/// - Returns: `CGRect` with given offset
func withOffset(_ offset: CGFloat) -> CGRect {
return CGRect(x: -offset, y: -offset,
width: self.width + 2.0 * offset,
height: self.height + 2.0 * offset)
}
}

View file

@ -20,6 +20,7 @@
@class RootTabEmptyView;
@class AnalyticsScreenTracker;
@class UserIndicatorStore;
@class RecentCellContextMenuProvider;
/**
Notification to be posted when recents data is ready. Notification object will be the RecentsViewController instance.
@ -103,6 +104,8 @@ FOUNDATION_EXPORT NSString *const RecentsViewControllerDataReadyNotification;
*/
@property (nonatomic, strong) UserIndicatorStore *userIndicatorStore;
@property (nonatomic, readonly) RecentCellContextMenuProvider *contextMenuProvider;
/**
Return the sticky header for the specified section of the table view
@ -198,6 +201,11 @@ Enable/disable the notifications for the selected room.
*/
- (void)openPublicRoom:(MXPublicRoom *)publicRoom;
/**
Show a room using its roomID
*/
- (void)showRoomWithRoomId:(NSString*)roomId inMatrixSession:(MXSession*)matrixSession;
#pragma mark - Scrolling
/**

View file

@ -36,7 +36,7 @@
NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewControllerDataReadyNotification";
@interface RecentsViewController () <CreateRoomCoordinatorBridgePresenterDelegate, RoomsDirectoryCoordinatorBridgePresenterDelegate, RoomNotificationSettingsCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, ExploreRoomCoordinatorBridgePresenterDelegate, SpaceChildRoomDetailBridgePresenterDelegate>
@interface RecentsViewController () <CreateRoomCoordinatorBridgePresenterDelegate, RoomsDirectoryCoordinatorBridgePresenterDelegate, RoomNotificationSettingsCoordinatorBridgePresenterDelegate, DialpadViewControllerDelegate, ExploreRoomCoordinatorBridgePresenterDelegate, SpaceChildRoomDetailBridgePresenterDelegate, RoomContextActionServiceDelegate>
{
// Tell whether a recents refresh is pending (suspended during editing mode).
BOOL isRefreshPending;
@ -138,6 +138,9 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
displayedSectionHeaders = [NSMutableArray array];
_contextMenuProvider = [RecentCellContextMenuProvider new];
self.contextMenuProvider.serviceDelegate = self;
// Set itself as delegate by default.
self.delegate = self;
}
@ -364,6 +367,15 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
- (void)refreshRecentsTable
{
MXLogDebug(@"[RecentsViewController]: Refreshing recents table view")
if (!self.recentsUpdateEnabled)
{
isRefreshNeeded = NO;
return;
}
isRefreshNeeded = NO;
// Refresh the tabBar icon badges
[[AppDelegate theDelegate].masterTabBarController refreshTabBarBadges];
@ -1032,6 +1044,12 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes
{
if (!self.recentsUpdateEnabled)
{
[super dataSource:dataSource didCellChange:changes];
return;
}
if ([changes isKindOfClass:NSIndexPath.class])
{
NSIndexPath *indexPath = (NSIndexPath *)changes;
@ -2487,4 +2505,78 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
}
}
#pragma mark - Context Menu
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0))
{
id<MXKRecentCellDataStoring> cellData = [self.dataSource cellDataAtIndexPath:indexPath];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (!cellData || !cell)
{
return nil;
}
self.recentsUpdateEnabled = NO;
return [self.contextMenuProvider contextMenuConfigurationWith:cellData from:cell session:self.dataSource.mxSession];
}
- (void)tableView:(UITableView *)tableView willPerformPreviewActionForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration animator:(id<UIContextMenuInteractionCommitAnimating>)animator API_AVAILABLE(ios(13.0))
{
NSString *roomId = [self.contextMenuProvider roomIdFrom:configuration.identifier];
if (!roomId)
{
self.recentsUpdateEnabled = YES;
return;
}
[animator addCompletion:^{
self.recentsUpdateEnabled = YES;
[self showRoomWithRoomId:roomId inMatrixSession:self.mainSession];
}];
}
- (UITargetedPreview *)tableView:(UITableView *)tableView previewForDismissingContextMenuWithConfiguration:(UIContextMenuConfiguration *)configuration API_AVAILABLE(ios(13.0))
{
self.recentsUpdateEnabled = YES;
return nil;
}
#pragma mark - RoomContextActionServiceDelegate
- (void)roomContextActionServiceDidJoinRoom:(id<RoomContextActionServiceProtocol>)service
{
[self showRoomWithRoomId:service.roomId inMatrixSession:service.session];
}
- (void)roomContextActionServiceDidLeaveRoom:(id<RoomContextActionServiceProtocol>)service
{
[self.userIndicatorStore presentSuccessWithLabel:VectorL10n.roomParticipantsLeaveSuccess];
}
- (void)roomContextActionService:(id<RoomContextActionServiceProtocol>)service presentAlert:(UIAlertController *)alertController
{
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)roomContextActionService:(id<RoomContextActionServiceProtocol>)service updateActivityIndicator:(BOOL)isActive
{
if (isActive)
{
[self startActivityIndicator];
}
else if ([self canStopActivityIndicator])
{
[self stopActivityIndicator];
}
}
- (void)roomContextActionService:(id<RoomContextActionServiceProtocol>)service showRoomNotificationSettingsForRoomWithId:(NSString *)roomId
{
editedRoomId = roomId;
[self changeEditedRoomNotificationSettings];
editedRoomId = nil;
}
@end

View file

@ -16,6 +16,8 @@
#import "MatrixKit.h"
@class PresenceIndicatorView;
/**
`RecentTableViewCell` instances display a room in the context of the recents list.
*/
@ -24,6 +26,7 @@
@property (weak, nonatomic) IBOutlet UIView *missedNotifAndUnreadIndicator;
@property (weak, nonatomic) IBOutlet MXKImageView *roomAvatar;
@property (weak, nonatomic) IBOutlet UIImageView *encryptedRoomIcon;
@property (weak, nonatomic) IBOutlet PresenceIndicatorView *presenceIndicatorView;
@property (weak, nonatomic) IBOutlet UILabel *missedNotifAndUnreadBadgeLabel;
@property (weak, nonatomic) IBOutlet UIView *missedNotifAndUnreadBadgeBgView;

View file

@ -128,6 +128,11 @@
roomId:roomCellData.roomIdentifier
displayName:roomCellData.roomDisplayname
mediaManager:roomCellData.mxSession.mediaManager];
// Presence indicator
self.presenceIndicatorView.borderColor = ThemeService.shared.theme.backgroundColor;
self.presenceIndicatorView.presence = roomCellData.presence;
self.presenceIndicatorView.hidden = roomCellData.presence == MXPresenceUnknown;
}
else
{

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -98,9 +98,24 @@
<constraint firstAttribute="height" constant="16" id="lNd-8B-gRr"/>
</constraints>
</imageView>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RDK-ky-osD" customClass="PresenceIndicatorView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="43" y="42" width="15" height="15"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="15" id="ZpE-ja-bYB"/>
<constraint firstAttribute="height" constant="15" id="xlV-Rh-dQN"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
<real key="value" value="1.5"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<constraints>
<constraint firstItem="F6K-PV-15f" firstAttribute="trailing" secondItem="360-Go-RcG" secondAttribute="trailing" id="3qB-Na-Eqs"/>
<constraint firstItem="RDK-ky-osD" firstAttribute="trailing" secondItem="RX5-eD-c3c" secondAttribute="trailing" constant="3" id="6kv-0l-fBz"/>
<constraint firstItem="RDK-ky-osD" firstAttribute="bottom" secondItem="RX5-eD-c3c" secondAttribute="bottom" id="HZp-I0-uYd"/>
<constraint firstItem="360-Go-RcG" firstAttribute="leading" secondItem="OeZ-wN-eil" secondAttribute="trailing" constant="8" id="KuE-8m-e9A"/>
<constraint firstItem="e7r-zL-9bw" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leading" id="PUW-if-ewh"/>
<constraint firstItem="Lg1-xQ-AGn" firstAttribute="leading" secondItem="RX5-eD-c3c" secondAttribute="trailing" constant="14" id="Pgp-JM-oQd"/>
@ -120,7 +135,7 @@
<constraint firstItem="RX5-eD-c3c" firstAttribute="leading" secondItem="aXz-IR-jj5" secondAttribute="leading" constant="13" id="tgy-cX-Wxm"/>
</constraints>
</tableViewCellContentView>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<accessibility key="accessibilityConfiguration" identifier="RecentTableViewCell"/>
<connections>
<outlet property="encryptedRoomIcon" destination="NAZ-zd-MHS" id="ZsH-Zr-Q4K"/>
@ -131,6 +146,7 @@
<outlet property="missedNotifAndUnreadBadgeBgViewWidthConstraint" destination="86T-0d-rAI" id="KLF-Du-rHT"/>
<outlet property="missedNotifAndUnreadBadgeLabel" destination="mbn-A1-1Yw" id="lee-qf-QjM"/>
<outlet property="missedNotifAndUnreadIndicator" destination="e7r-zL-9bw" id="jWE-0y-BMv"/>
<outlet property="presenceIndicatorView" destination="RDK-ky-osD" id="kj3-j2-QHH"/>
<outlet property="roomAvatar" destination="RX5-eD-c3c" id="dIC-8p-inL"/>
<outlet property="roomTitle" destination="Lg1-xQ-AGn" id="q7Q-TM-5C8"/>
<outlet property="unsentImageView" destination="F6K-PV-15f" id="EnH-mb-j6s"/>

View file

@ -32,7 +32,14 @@ class VectorHostingController: UIHostingController<AnyView> {
// MARK: Public
/// Whether or not to use the iOS 15 style scroll edge appearance when the controller has a navigation bar.
var enableNavigationBarScrollEdgeAppearance = false
/// When non-nil, the style will be applied to the status bar.
var statusBarStyle: UIStatusBarStyle?
override var preferredStatusBarStyle: UIStatusBarStyle {
statusBarStyle ?? super.preferredStatusBarStyle
}
init<Content>(rootView: Content) where Content: View {
self.theme = ThemeService.shared().theme

View file

@ -150,7 +150,9 @@
if (contact.isMatrixContact)
{
// Observe contact presence change
MXWeakify(self);
mxPresenceObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKContactManagerMatrixUserPresenceChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
MXStrongifyAndReturnIfNil(self);
NSString* matrixId = self.firstMatrixId;

View file

@ -0,0 +1,51 @@
//
// 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
/// `PublicRoomActionProvider` provides the menu for `MXPUblicRoom` instances
@available(iOS 13.0, *)
class PublicRoomActionProvider: RoomActionProviderProtocol {
// MARK: - Properties
private let publicRoom: MXPublicRoom
private let service: UnownedRoomContextActionService
// MARK: - Setup
init(publicRoom: MXPublicRoom, service: UnownedRoomContextActionService) {
self.publicRoom = publicRoom
self.service = service
}
// MARK: - RoomActionProviderProtocol
var menu: UIMenu {
return UIMenu(children: [
self.joinAction
])
}
// MARK: - Private
private var joinAction: UIAction {
return UIAction(
title: VectorL10n.join) { [weak self] action in
self?.service.joinRoom()
}
}
}

View file

@ -0,0 +1,147 @@
//
// 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
/// `RoomActionProvider` provides the menu for `MXRoom` instances
@available(iOS 13.0, *)
class RoomActionProvider: RoomActionProviderProtocol {
// MARK: - Properties
private let service: RoomContextActionService
// MARK: - Setup
init(service: RoomContextActionService) {
self.service = service
}
// MARK: - RoomActionProviderProtocol
var menu: UIMenu {
if service.isRoomJoined {
return UIMenu(children: [
self.directChatAction,
self.notificationsAction,
self.favouriteAction,
self.lowPriorityAction,
self.leaveAction
])
} else {
if service.roomMembership == .invite {
return UIMenu(children: [
self.acceptInviteAction,
self.declineInviteAction
])
} else {
return UIMenu(children: [
self.joinAction
])
}
}
}
// MARK: - Private
private var directChatAction: UIAction {
return UIAction(
title: service.isRoomDirect ? VectorL10n.homeContextMenuMakeRoom : VectorL10n.homeContextMenuMakeDm,
image: UIImage(systemName: service.isRoomDirect ? "person.crop.circle.badge.xmark" : "person.circle")) { [weak self] action in
guard let self = self else { return }
self.service.isRoomDirect = !self.service.isRoomDirect
}
}
private var notificationsAction: UIAction {
let notificationsImage: UIImage?
let notificationsTitle: String
if BuildSettings.showNotificationsV2 {
notificationsTitle = VectorL10n.homeContextMenuNotifications
notificationsImage = UIImage(systemName: "bell")
} else {
notificationsTitle = service.isRoomMuted ? VectorL10n.homeContextMenuUnmute : VectorL10n.homeContextMenuMute
notificationsImage = UIImage(systemName: service.isRoomMuted ? "bell.slash": "bell")
}
return UIAction(
title: notificationsTitle,
image: notificationsImage) { [weak self] action in
guard let self = self else { return }
self.service.isRoomMuted = !self.service.isRoomMuted
}
}
private var favouriteAction: UIAction {
return UIAction(
title: self.service.isRoomFavourite ? VectorL10n.homeContextMenuUnfavourite : VectorL10n.homeContextMenuFavourite,
image: UIImage(systemName: self.service.isRoomFavourite ? "star.slash" : "star")) { [weak self] action in
guard let self = self else { return }
self.service.isRoomFavourite = !self.service.isRoomFavourite
}
}
private var lowPriorityAction: UIAction {
return UIAction(
title: self.service.isRoomLowPriority ? VectorL10n.homeContextMenuNormalPriority : VectorL10n.homeContextMenuLowPriority,
image: UIImage(systemName: self.service.isRoomLowPriority ? "arrow.up" : "arrow.down")) { [weak self] action in
guard let self = self else { return }
self.service.isRoomLowPriority = !self.service.isRoomLowPriority
}
}
private var leaveAction: UIAction {
let image: UIImage?
if #available(iOS 14.0, *) {
image = UIImage(systemName: "rectangle.righthalf.inset.fill.arrow.right")
} else {
image = UIImage(systemName: "rectangle.xmark")
}
let action = UIAction(title: VectorL10n.homeContextMenuLeave, image: image) { [weak self] action in
guard let self = self else { return }
self.service.leaveRoom(promptUser: true)
}
action.attributes = .destructive
return action
}
private var acceptInviteAction: UIAction {
return UIAction(
title: VectorL10n.accept) { [weak self] action in
guard let self = self else { return }
self.service.joinRoom()
}
}
private var declineInviteAction: UIAction {
let action = UIAction(
title: VectorL10n.decline) { [weak self] action in
guard let self = self else { return }
self.service.leaveRoom(promptUser: false)
}
action.attributes = .destructive
return action
}
private var joinAction: UIAction {
return UIAction(
title: VectorL10n.join) { [weak self] action in
guard let self = self else { return }
self.service.joinRoom()
}
}
}

View file

@ -0,0 +1,24 @@
//
// 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
/// Classes compliant with the protocol `RoomActionProviderProtocol` are meant to provide the menu within `UIContextMenuActionProvider`
@available(iOS 13.0, *)
protocol RoomActionProviderProtocol {
/// menu instance returned within the `UIContextMenuActionProvider` block
var menu: UIMenu { get }
}

View file

@ -0,0 +1,51 @@
//
// 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
/// `SpaceChildActionProvider` provides the menu for `MXSpaceChildInfo` instances
@available(iOS 13.0, *)
class SpaceChildActionProvider: RoomActionProviderProtocol {
// MARK: - Properties
private let spaceChildInfo: MXSpaceChildInfo
private let service: UnownedRoomContextActionService
// MARK: - Setup
init(spaceChildInfo: MXSpaceChildInfo, service: UnownedRoomContextActionService) {
self.spaceChildInfo = spaceChildInfo
self.service = service
}
// MARK: - RoomActionProviderProtocol
var menu: UIMenu {
return UIMenu(children: [
self.joinAction
])
}
// MARK: - Private
private var joinAction: UIAction {
return UIAction(
title: VectorL10n.join) { [weak self] action in
self?.service.joinRoom()
}
}
}

View file

@ -0,0 +1,83 @@
//
// 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
/// Helper class `PublicRoomContextMenuProvider` that provides an instace of `UIContextMenuConfiguration` from an instance of `MXPublicRoom`
@objcMembers
class PublicRoomContextMenuProvider: NSObject {
weak var serviceDelegate: RoomContextActionServiceDelegate?
private var currentService: RoomContextActionServiceProtocol?
@available(iOS 13.0, *)
func contextMenuConfiguration(with publicRoom: MXPublicRoom, from cell: UIView, session: MXSession) -> UIContextMenuConfiguration? {
if let room = session.room(withRoomId: publicRoom.roomId) {
let service = RoomContextActionService(room: room, delegate: serviceDelegate)
self.currentService = service
let actionProvider = RoomActionProvider(service: service)
return UIContextMenuConfiguration(identifier: publicRoom.jsonString() as? NSString) {
if room.summary?.isJoined == true {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let roomViewController = storyboard.instantiateViewController(withIdentifier: "RoomViewControllerStoryboardId") as? RoomViewController else {
return nil
}
roomViewController.isContextPreview = true
let roomDataSourceManager = MXKRoomDataSourceManager.sharedManager(forMatrixSession: session)
roomDataSourceManager?.roomDataSource(forRoom: room.roomId, create: true, onComplete: { roomDataSource in
roomViewController.displayRoom(roomDataSource)
})
return roomViewController
} else {
let viewModel = RoomContextPreviewViewModel(room: room)
return RoomContextPreviewViewController.instantiate(with: viewModel, mediaManager: session.mediaManager)
}
} actionProvider: { suggestedActions in
return actionProvider.menu
}
} else {
let service = UnownedRoomContextActionService(roomId: publicRoom.roomId, canonicalAlias: publicRoom.canonicalAlias, session: session, delegate: serviceDelegate)
self.currentService = service
let actionProvider = PublicRoomActionProvider(publicRoom: publicRoom, service: service)
return UIContextMenuConfiguration(identifier: publicRoom.jsonString() as? NSString) {
let viewModel = PublicRoomContextPreviewViewModel(publicRoom: publicRoom)
return RoomContextPreviewViewController.instantiate(with: viewModel, mediaManager: session.mediaManager)
} actionProvider: { suggestedActions in
return actionProvider.menu
}
}
}
func publicRoom(from identifier: NSCopying) -> MXPublicRoom? {
guard let jsonString = identifier as? String, let data = jsonString.data(using: .utf8) else {
return nil
}
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
guard let publicRoom = MXPublicRoom(fromJSON: json) else {
return nil
}
return publicRoom
} catch {
return nil
}
}
}

View file

@ -0,0 +1,72 @@
//
// 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
/// Helper class `RecentCellContextMenuProvider` that provides an instace of `UIContextMenuConfiguration` from an instance of `MXKRecentCellDataStoring`
@objcMembers
class RecentCellContextMenuProvider: NSObject {
weak var serviceDelegate: RoomContextActionServiceDelegate?
private var currentService: RoomContextActionServiceProtocol?
@available(iOS 13.0, *)
func contextMenuConfiguration(with cellData: MXKRecentCellDataStoring, from cell: UIView, session: MXSession) -> UIContextMenuConfiguration? {
if cellData.isSuggestedRoom, let childInfo = cellData.roomSummary.spaceChildInfo {
let service = UnownedRoomContextActionService(roomId: childInfo.childRoomId, canonicalAlias: childInfo.canonicalAlias, session: session, delegate: serviceDelegate)
self.currentService = service
let actionProvider = SpaceChildActionProvider(spaceChildInfo: childInfo, service: service)
return UIContextMenuConfiguration(identifier: "" as NSString) {
let viewModel = SpaceChildContextPreviewViewModel(childInfo: childInfo)
return RoomContextPreviewViewController.instantiate(with: viewModel, mediaManager: session.mediaManager)
} actionProvider: { suggestedActions in
return actionProvider.menu
}
} else if let room = session.room(withRoomId: cellData.roomIdentifier) {
let service = RoomContextActionService(room: room, delegate: serviceDelegate)
self.currentService = service
let actionProvider = RoomActionProvider(service: service)
return UIContextMenuConfiguration(identifier: cellData.roomIdentifier as NSString) {
if room.summary?.isJoined == true {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let roomViewController = storyboard.instantiateViewController(withIdentifier: "RoomViewControllerStoryboardId") as? RoomViewController else {
return nil
}
roomViewController.isContextPreview = true
let roomDataSourceManager = MXKRoomDataSourceManager.sharedManager(forMatrixSession: session)
roomDataSourceManager?.roomDataSource(forRoom: room.roomId, create: true, onComplete: { roomDataSource in
roomViewController.displayRoom(roomDataSource)
})
return roomViewController
} else {
let viewModel = RoomContextPreviewViewModel(room: room)
return RoomContextPreviewViewController.instantiate(with: viewModel, mediaManager: session.mediaManager)
}
} actionProvider: { suggestedActions in
return actionProvider.menu
}
}
return nil
}
func roomId(from identifier: NSCopying) -> String? {
let roomId = identifier as? String
return roomId?.isEmpty == true ? nil : roomId
}
}

View file

@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Room Context Preview View Controller-->
<scene sceneID="mt5-wz-YKA">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="RoomContextPreviewViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="znu-Di-7R2">
<rect key="frame" x="16" y="16" width="382" height="131"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="AsV-bL-S4I">
<rect key="frame" x="0.0" y="0.0" width="382" height="39"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="EKj-Sq-fNj" customClass="UserAvatarView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="30" height="30"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" secondItem="EKj-Sq-fNj" secondAttribute="height" multiplier="1:1" id="tAH-7o-D2o"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dP7-1l-M4g">
<rect key="frame" x="46" y="0.0" width="336" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZrC-Yh-2oS">
<rect key="frame" x="46" y="9.5" width="336" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5yf-Ya-F94">
<rect key="frame" x="0.0" y="38" width="382" height="1"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="height" constant="1" id="oaQ-Mn-s9h"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="5yf-Ya-F94" secondAttribute="trailing" id="0Mb-v0-zDA"/>
<constraint firstAttribute="trailing" secondItem="dP7-1l-M4g" secondAttribute="trailing" id="4Bn-35-l7m"/>
<constraint firstItem="ZrC-Yh-2oS" firstAttribute="bottom" secondItem="5yf-Ya-F94" secondAttribute="top" constant="-8" id="4F5-OJ-5Qo"/>
<constraint firstItem="EKj-Sq-fNj" firstAttribute="leading" secondItem="AsV-bL-S4I" secondAttribute="leading" id="6MQ-OG-IyN"/>
<constraint firstItem="5yf-Ya-F94" firstAttribute="leading" secondItem="AsV-bL-S4I" secondAttribute="leading" id="LzM-jh-D4A"/>
<constraint firstItem="EKj-Sq-fNj" firstAttribute="top" secondItem="AsV-bL-S4I" secondAttribute="top" id="Q5b-Qb-dG7"/>
<constraint firstAttribute="height" constant="39" id="RBg-nZ-j8u"/>
<constraint firstAttribute="bottom" secondItem="5yf-Ya-F94" secondAttribute="bottom" id="SSX-43-Q99"/>
<constraint firstItem="dP7-1l-M4g" firstAttribute="top" secondItem="AsV-bL-S4I" secondAttribute="top" id="Xmt-oa-GUo"/>
<constraint firstItem="EKj-Sq-fNj" firstAttribute="bottom" secondItem="5yf-Ya-F94" secondAttribute="top" constant="-8" id="ZAv-Bu-OpG"/>
<constraint firstItem="ZrC-Yh-2oS" firstAttribute="leading" secondItem="EKj-Sq-fNj" secondAttribute="trailing" constant="16" id="euI-Qa-vA8"/>
<constraint firstAttribute="trailing" secondItem="ZrC-Yh-2oS" secondAttribute="trailing" id="tfu-em-Spa"/>
<constraint firstItem="dP7-1l-M4g" firstAttribute="leading" secondItem="EKj-Sq-fNj" secondAttribute="trailing" constant="16" id="xru-cM-8E7"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3Zk-nA-8Um">
<rect key="frame" x="0.0" y="55" width="382" height="40"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aYH-QK-OjX" customClass="SpaceAvatarView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="40" height="40"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GFn-cD-R7B" customClass="RoomAvatarView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="40" height="40"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" secondItem="GFn-cD-R7B" secondAttribute="height" multiplier="1:1" id="FT0-YM-lPC"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="A message" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bxI-mu-qng">
<rect key="frame" x="56" y="11" width="326" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="aYH-QK-OjX" firstAttribute="trailing" secondItem="GFn-cD-R7B" secondAttribute="trailing" id="7D1-PY-FVu"/>
<constraint firstItem="bxI-mu-qng" firstAttribute="leading" secondItem="GFn-cD-R7B" secondAttribute="trailing" constant="16" id="8yl-K3-aPF"/>
<constraint firstItem="aYH-QK-OjX" firstAttribute="top" secondItem="GFn-cD-R7B" secondAttribute="top" id="C8f-9d-dCs"/>
<constraint firstAttribute="bottom" secondItem="GFn-cD-R7B" secondAttribute="bottom" id="EM5-Rl-13P"/>
<constraint firstItem="aYH-QK-OjX" firstAttribute="leading" secondItem="GFn-cD-R7B" secondAttribute="leading" id="T5I-e9-Wzc"/>
<constraint firstAttribute="height" constant="40" id="Vmz-ZU-53A"/>
<constraint firstAttribute="trailing" secondItem="bxI-mu-qng" secondAttribute="trailing" id="ag1-fO-WU8"/>
<constraint firstItem="bxI-mu-qng" firstAttribute="centerY" secondItem="GFn-cD-R7B" secondAttribute="centerY" id="fFa-xZ-9Ua"/>
<constraint firstItem="GFn-cD-R7B" firstAttribute="top" secondItem="3Zk-nA-8Um" secondAttribute="top" id="ok0-nJ-ZOx"/>
<constraint firstItem="aYH-QK-OjX" firstAttribute="bottom" secondItem="GFn-cD-R7B" secondAttribute="bottom" id="q99-dZ-5gE"/>
<constraint firstItem="GFn-cD-R7B" firstAttribute="leading" secondItem="3Zk-nA-8Um" secondAttribute="leading" id="vEa-Je-SMe"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SiK-XL-8vu">
<rect key="frame" x="0.0" y="111" width="382" height="20"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="space_user_icon" translatesAutoresizingMaskIntoConstraints="NO" id="csm-jO-Pai">
<rect key="frame" x="0.0" y="2" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="fyF-zL-p9K"/>
<constraint firstAttribute="height" constant="16" id="q4Q-vf-9NW"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="44" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AL2-9M-fyr">
<rect key="frame" x="21" y="2" width="17" height="16"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="space_room_icon" translatesAutoresizingMaskIntoConstraints="NO" id="GIX-Uh-g6e">
<rect key="frame" x="46" y="2" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="Sze-Z0-VGR"/>
<constraint firstAttribute="height" constant="16" id="aeM-ff-fuq"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="44" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eCr-BQ-Bl6">
<rect key="frame" x="66" y="2" width="17" height="16"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="B2R-Tu-sci">
<rect key="frame" x="91" y="0.0" width="77.5" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Description" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bKu-1p-huC">
<rect key="frame" x="4" y="2" width="69.5" height="16"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="bKu-1p-huC" firstAttribute="top" secondItem="B2R-Tu-sci" secondAttribute="top" constant="2" id="5Al-wx-tba"/>
<constraint firstAttribute="trailing" secondItem="bKu-1p-huC" secondAttribute="trailing" constant="4" id="Eu3-Ll-8kv"/>
<constraint firstItem="bKu-1p-huC" firstAttribute="leading" secondItem="B2R-Tu-sci" secondAttribute="leading" constant="4" id="Yjc-CW-w7C"/>
<constraint firstAttribute="bottom" secondItem="bKu-1p-huC" secondAttribute="bottom" constant="2" id="pZQ-47-Sfk"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="GIX-Uh-g6e" firstAttribute="leading" secondItem="AL2-9M-fyr" secondAttribute="trailing" constant="8" id="1bK-ze-Syb"/>
<constraint firstItem="eCr-BQ-Bl6" firstAttribute="firstBaseline" secondItem="AL2-9M-fyr" secondAttribute="firstBaseline" id="4yt-we-Apx"/>
<constraint firstItem="AL2-9M-fyr" firstAttribute="leading" secondItem="csm-jO-Pai" secondAttribute="trailing" constant="5" id="98p-1f-HbL"/>
<constraint firstItem="eCr-BQ-Bl6" firstAttribute="leading" secondItem="GIX-Uh-g6e" secondAttribute="trailing" constant="4" id="ClR-sl-8nH"/>
<constraint firstItem="eCr-BQ-Bl6" firstAttribute="centerY" secondItem="SiK-XL-8vu" secondAttribute="centerY" id="DpH-iZ-dIl"/>
<constraint firstItem="B2R-Tu-sci" firstAttribute="centerY" secondItem="SiK-XL-8vu" secondAttribute="centerY" id="Fn4-iu-FJC"/>
<constraint firstItem="B2R-Tu-sci" firstAttribute="leading" secondItem="eCr-BQ-Bl6" secondAttribute="trailing" constant="8" id="NyB-1f-7xY"/>
<constraint firstItem="AL2-9M-fyr" firstAttribute="centerY" secondItem="csm-jO-Pai" secondAttribute="centerY" id="PPc-RX-m94"/>
<constraint firstItem="GIX-Uh-g6e" firstAttribute="centerY" secondItem="SiK-XL-8vu" secondAttribute="centerY" id="SCW-Ni-sFb"/>
<constraint firstItem="csm-jO-Pai" firstAttribute="centerY" secondItem="SiK-XL-8vu" secondAttribute="centerY" id="Y6I-AE-5Xm"/>
<constraint firstItem="csm-jO-Pai" firstAttribute="leading" secondItem="SiK-XL-8vu" secondAttribute="leading" id="gdX-GC-yGW"/>
<constraint firstItem="GIX-Uh-g6e" firstAttribute="centerY" secondItem="csm-jO-Pai" secondAttribute="centerY" id="nnm-Ho-YpY"/>
<constraint firstItem="AL2-9M-fyr" firstAttribute="centerY" secondItem="SiK-XL-8vu" secondAttribute="centerY" id="qrq-ik-CUC"/>
<constraint firstAttribute="height" constant="20" id="rkj-PY-ju5"/>
<constraint firstItem="B2R-Tu-sci" firstAttribute="centerY" secondItem="csm-jO-Pai" secondAttribute="centerY" id="uJA-pp-qzA"/>
</constraints>
</view>
</subviews>
</stackView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMm-p7-j1q">
<rect key="frame" x="16" y="163" width="382" height="663"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="bFg-jh-JZB" firstAttribute="bottom" secondItem="pMm-p7-j1q" secondAttribute="bottom" constant="16" id="6RZ-54-9VV"/>
<constraint firstItem="pMm-p7-j1q" firstAttribute="top" secondItem="znu-Di-7R2" secondAttribute="bottom" constant="16" id="Lv3-JM-HG3"/>
<constraint firstItem="znu-Di-7R2" firstAttribute="top" secondItem="bFg-jh-JZB" secondAttribute="top" constant="16" id="a9z-8B-zCB"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="pMm-p7-j1q" secondAttribute="trailing" constant="16" id="eJM-Nh-STn"/>
<constraint firstItem="znu-Di-7R2" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" constant="16" id="h3I-ze-XQN"/>
<constraint firstItem="pMm-p7-j1q" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" constant="16" id="hwl-gf-uti"/>
<constraint firstItem="znu-Di-7R2" firstAttribute="trailing" secondItem="bFg-jh-JZB" secondAttribute="trailing" constant="-16" id="zNF-fN-X5X"/>
</constraints>
</view>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<modalPageSheetSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="avatarView" destination="GFn-cD-R7B" id="VCB-vM-Kyk"/>
<outlet property="inviteDetailLabel" destination="ZrC-Yh-2oS" id="MOg-0I-G4r"/>
<outlet property="inviteHeaderView" destination="AsV-bL-S4I" id="vTP-qj-zWM"/>
<outlet property="inviteSeparatorView" destination="5yf-Ya-F94" id="4Ur-6D-6id"/>
<outlet property="inviteTitleLabel" destination="dP7-1l-M4g" id="lNf-xH-LgV"/>
<outlet property="inviterAvatarView" destination="EKj-Sq-fNj" id="3c7-00-rxm"/>
<outlet property="membersLabel" destination="AL2-9M-fyr" id="Nij-ha-A7f"/>
<outlet property="roomsIconView" destination="GIX-Uh-g6e" id="lW2-zB-8wl"/>
<outlet property="roomsLabel" destination="eCr-BQ-Bl6" id="oA0-Ah-CAJ"/>
<outlet property="spaceAvatarView" destination="aYH-QK-OjX" id="M5D-r3-6HA"/>
<outlet property="spaceTagLabel" destination="bKu-1p-huC" id="0xv-Ic-puM"/>
<outlet property="spaceTagView" destination="B2R-Tu-sci" id="qTf-Vw-Ydz"/>
<outlet property="stackView" destination="znu-Di-7R2" id="iBU-H0-d7a"/>
<outlet property="titleLabel" destination="bxI-mu-qng" id="pbX-aZ-inC"/>
<outlet property="topicLabel" destination="pMm-p7-j1q" id="EMb-CK-1bp"/>
<outlet property="topicLabelBottomMargin" destination="6RZ-54-9VV" id="6PP-GA-WAl"/>
<outlet property="userIconView" destination="csm-jO-Pai" id="QAD-Zt-foS"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3198.5507246376815" y="-647.54464285714278"/>
</scene>
</scenes>
<resources>
<image name="space_room_icon" width="16" height="16"/>
<image name="space_user_icon" width="14" height="14"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View file

@ -17,8 +17,10 @@
*/
import UIKit
import MatrixSDK
final class SpaceRoomPreviewViewController: UIViewController {
/// `RoomContextPreviewViewController` is used to dsplay room preview data within a `UIContextMenuContentPreviewProvider`
final class RoomContextPreviewViewController: UIViewController {
// MARK: - Constants
@ -39,19 +41,25 @@ final class SpaceRoomPreviewViewController: UIViewController {
@IBOutlet private weak var topicLabelBottomMargin: NSLayoutConstraint!
@IBOutlet private weak var spaceTagView: UIView!
@IBOutlet private weak var spaceTagLabel: UILabel!
@IBOutlet private weak var stackView: UIStackView!
@IBOutlet private weak var inviteHeaderView: UIView!
@IBOutlet private weak var inviterAvatarView: UserAvatarView!
@IBOutlet private weak var inviteTitleLabel: UILabel!
@IBOutlet private weak var inviteDetailLabel: UILabel!
@IBOutlet private weak var inviteSeparatorView: UIView!
// MARK: Private
private var theme: Theme!
private var roomInfo: MXSpaceChildInfo!
private var avatarViewData: AvatarViewDataProtocol!
private var viewModel: RoomContextPreviewViewModelProtocol!
private var mediaManager: MXMediaManager?
// MARK: - Setup
class func instantiate(with roomInfo: MXSpaceChildInfo, avatarViewData: AvatarViewDataProtocol!) -> SpaceRoomPreviewViewController {
let viewController = StoryboardScene.SpaceRoomPreviewViewController.initialScene.instantiate()
viewController.roomInfo = roomInfo
viewController.avatarViewData = avatarViewData
class func instantiate(with viewModel: RoomContextPreviewViewModelProtocol, mediaManager: MXMediaManager?) -> RoomContextPreviewViewController {
let viewController = StoryboardScene.RoomContextPreviewViewController.initialScene.instantiate()
viewController.viewModel = viewModel
viewController.mediaManager = mediaManager
viewController.theme = ThemeService.shared().theme
return viewController
}
@ -62,9 +70,12 @@ final class SpaceRoomPreviewViewController: UIViewController {
super.viewDidLoad()
// Do any additional setup after loading the view.
viewModel.viewDelegate = self
setupView()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
self.viewModel.process(viewAction: .loadData)
}
override var preferredContentSize: CGSize {
@ -105,6 +116,15 @@ final class SpaceRoomPreviewViewController: UIViewController {
self.spaceTagView.backgroundColor = theme.colors.quinaryContent
self.spaceTagLabel.font = theme.fonts.caption1
self.spaceTagLabel.textColor = theme.colors.tertiaryContent
self.inviteTitleLabel.textColor = theme.colors.tertiaryContent
self.inviteTitleLabel.font = theme.fonts.calloutSB
self.inviteDetailLabel.textColor = theme.colors.tertiaryContent
self.inviteDetailLabel.font = theme.fonts.caption1
self.inviteSeparatorView.backgroundColor = theme.colors.quinaryContent
self.inviterAvatarView.alpha = 0.7
}
private func registerThemeServiceDidChangeThemeNotification() {
@ -115,16 +135,19 @@ final class SpaceRoomPreviewViewController: UIViewController {
self.update(theme: ThemeService.shared().theme)
}
private func setupView() {
self.titleLabel.text = roomInfo.displayName
private func renderLoaded(with parameters: RoomContextPreviewLoadedParameters) {
self.titleLabel.text = parameters.displayName
self.spaceTagView.layer.masksToBounds = true
self.spaceTagView.layer.cornerRadius = 2
self.spaceTagView.isHidden = roomInfo.roomType != .space
self.spaceTagLabel.text = VectorL10n.spaceTag
self.spaceTagView.isHidden = parameters.roomType != .space
self.avatarView.isHidden = roomInfo.roomType == .space
self.spaceAvatarView.isHidden = roomInfo.roomType != .space
self.avatarView.isHidden = parameters.roomType == .space
self.spaceAvatarView.isHidden = parameters.roomType != .space
let avatarViewData = AvatarViewData(matrixItemId: parameters.roomId,
displayName: parameters.displayName,
avatarUrl: parameters.avatarUrl,
mediaManager: mediaManager,
fallbackImage: .matrixItem(parameters.roomId, parameters.displayName))
if !self.avatarView.isHidden {
self.avatarView.fill(with: avatarViewData)
@ -132,17 +155,46 @@ final class SpaceRoomPreviewViewController: UIViewController {
if !self.spaceAvatarView.isHidden {
self.spaceAvatarView.fill(with: avatarViewData)
}
self.membersLabel.text = roomInfo.activeMemberCount == 1 ? VectorL10n.roomTitleOneMember : VectorL10n.roomTitleMembers("\(roomInfo.activeMemberCount)")
if roomInfo.childrenIds.count == 1 {
self.roomsLabel.text = VectorL10n.spacesExploreRoomsOneRoom
} else {
self.roomsLabel.text = VectorL10n.spacesExploreRoomsRoomNumber("\(roomInfo.childrenIds.count)")
if parameters.membership != .invite {
self.stackView.removeArrangedSubview(self.inviteHeaderView)
self.inviteHeaderView.isHidden = true
}
self.topicLabel.text = roomInfo.topic
self.membersLabel.text = parameters.membersCount == 1 ? VectorL10n.roomTitleOneMember : VectorL10n.roomTitleMembers("\(parameters.membersCount)")
if let inviterId = parameters.inviterId {
if let inviter = parameters.inviter {
let avatarData = AvatarViewData(matrixItemId: inviterId,
displayName: inviter.displayname,
avatarUrl: inviter.avatarUrl,
mediaManager: mediaManager,
fallbackImage: .matrixItem(inviterId, inviter.displayname))
self.inviterAvatarView.fill(with: avatarData)
if let inviterName = inviter.displayname {
self.inviteTitleLabel.text = VectorL10n.noticeRoomInviteYou(inviterName)
self.inviteDetailLabel.text = inviterId
} else {
self.inviteTitleLabel.text = VectorL10n.noticeRoomInviteYou(inviterId)
}
} else {
self.inviteTitleLabel.text = VectorL10n.noticeRoomInviteYou(inviterId)
}
}
self.topicLabel.text = parameters.topic
topicLabelBottomMargin.constant = self.topicLabel.text.isEmptyOrNil ? 0 : 16
self.roomsIconView.isHidden = roomInfo.roomType != .space
self.roomsLabel.isHidden = roomInfo.roomType != .space
self.roomsIconView.isHidden = parameters.roomType != .space
self.roomsLabel.isHidden = parameters.roomType != .space
self.view.layoutIfNeeded()
}
private func setupView() {
self.spaceTagView.layer.masksToBounds = true
self.spaceTagView.layer.cornerRadius = 2
self.spaceTagLabel.text = VectorL10n.spaceTag
}
private func intrisicHeight(with width: CGFloat) -> CGFloat {
@ -154,3 +206,14 @@ final class SpaceRoomPreviewViewController: UIViewController {
return self.topicLabel.frame.minY + topicHeight + 16
}
}
// MARK: - RoomContextPreviewViewModelViewDelegate
extension RoomContextPreviewViewController: RoomContextPreviewViewModelViewDelegate {
func roomContextPreviewViewModel(_ viewModel: RoomContextPreviewViewModelProtocol, didUpdateViewState viewSate: RoomContextPreviewViewState) {
switch viewSate {
case .loaded(let parameters):
self.renderLoaded(with: parameters)
}
}
}

View file

@ -0,0 +1,178 @@
//
// 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
/// `RoomContextActionService` implements all the possible actions for an instance of `MXRoom`
class RoomContextActionService: NSObject, RoomContextActionServiceProtocol {
// MARK: - RoomContextActionServiceProtocol
private(set) var session: MXSession
var roomId: String {
return room.roomId
}
internal weak var delegate: RoomContextActionServiceDelegate?
// MARK: - Properties
private let room: MXRoom
private let unownedRoomService: UnownedRoomContextActionService
// MARK: - Setup
init(room: MXRoom, delegate: RoomContextActionServiceDelegate?) {
self.room = room
self.delegate = delegate
self.isRoomJoined = room.summary?.isJoined ?? false
self.roomMembership = room.summary?.membership ?? .unknown
self.session = room.mxSession
self.unownedRoomService = UnownedRoomContextActionService(roomId: room.roomId, canonicalAlias: room.summary?.aliases?.first, session: self.session, delegate: delegate)
}
// MARK: - Public
let isRoomJoined: Bool
let roomMembership: MXMembership
var isRoomDirect: Bool {
get {
return room.isDirect
}
set {
delegate?.roomContextActionService(self, updateActivityIndicator: true)
room.setIsDirect(newValue, withUserId: nil) { [weak self] in
guard let self = self else { return }
self.delegate?.roomContextActionService(self, updateActivityIndicator: false)
} failure: { [weak self] error in
guard let self = self else { return }
self.delegate?.roomContextActionService(self, updateActivityIndicator: false)
// Notify the end user
if let userId = self.session.myUserId {
NotificationCenter.default.post(name: NSNotification.Name.mxkError, object: error, userInfo: [kMXKErrorUserIdKey: userId])
} else {
NotificationCenter.default.post(name: NSNotification.Name.mxkError, object: error)
}
}
}
}
var isRoomMuted: Bool {
get {
return room.isMuted || room.isMentionsOnly
}
set {
if BuildSettings.showNotificationsV2 {
self.delegate?.roomContextActionService(self, showRoomNotificationSettingsForRoomWithId: room.roomId)
} else {
self.muteRoomNotifications(newValue)
}
}
}
var isRoomFavourite: Bool {
get {
let currentTag = room.accountData.tags?.values.first
return currentTag?.name == kMXRoomTagFavourite
}
set {
self.updateRoom(tag: newValue ? kMXRoomTagFavourite : nil)
}
}
var isRoomLowPriority: Bool {
get {
let currentTag = room.accountData.tags?.values.first
return currentTag?.name == kMXRoomTagLowPriority
}
set {
self.updateRoom(tag: newValue ? kMXRoomTagLowPriority : nil)
}
}
private func muteRoomNotifications(_ isMuted: Bool) {
self.delegate?.roomContextActionService(self, updateActivityIndicator: true)
if isMuted {
room.mentionsOnly { [weak self] in
guard let self = self else { return }
self.delegate?.roomContextActionService(self, updateActivityIndicator: false)
}
} else {
room.allMessages { [weak self] in
guard let self = self else { return }
self.delegate?.roomContextActionService(self, updateActivityIndicator: false)
}
}
}
private func updateRoom(tag: String?) {
self.delegate?.roomContextActionService(self, updateActivityIndicator: true)
room.setRoomTag(tag) {
self.delegate?.roomContextActionService(self, updateActivityIndicator: false)
}
}
func leaveRoom(promptUser: Bool) {
guard promptUser else {
self.leaveRoom()
return
}
let title = room.isDirect ? VectorL10n.roomParticipantsLeavePromptTitleForDm : VectorL10n.roomParticipantsLeavePromptTitle
let message = room.isDirect ? VectorL10n.roomParticipantsLeavePromptMsgForDm : VectorL10n.roomParticipantsLeavePromptMsg
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: VectorL10n.cancel, style: .cancel, handler: nil))
alertController.addAction(UIAlertAction(title: VectorL10n.leave, style: .default, handler: { action in
self.leaveRoom()
}))
self.delegate?.roomContextActionService(self, presentAlert: alertController)
}
func joinRoom() {
unownedRoomService.joinRoom()
}
private func leaveRoom() {
self.delegate?.roomContextActionService(self, updateActivityIndicator: true)
// cancel pending uploads/downloads
// they are useless by now
MXMediaManager.cancelDownloads(inCacheFolder: self.room.roomId)
// TODO: GFO cancel pending uploads related to this room
MXLog.debug("[RoomContextActionService] leaving room \(self.room.roomId ?? "nil")")
self.room.leave { [weak self] response in
guard let self = self else { return }
switch response {
case .success:
self.delegate?.roomContextActionService(self, updateActivityIndicator: false)
self.delegate?.roomContextActionServiceDidLeaveRoom(self)
case .failure(let error):
self.delegate?.roomContextActionService(self, updateActivityIndicator: false)
// Notify the end user
if let userId = self.session.myUserId {
NotificationCenter.default.post(name: NSNotification.Name.mxkError, object: error, userInfo: [kMXKErrorUserIdKey: userId])
} else {
NotificationCenter.default.post(name: NSNotification.Name.mxkError, object: error)
}
}
}
}
}

View file

@ -0,0 +1,32 @@
//
// 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
@objc protocol RoomContextActionServiceDelegate {
func roomContextActionService(_ service: RoomContextActionServiceProtocol, updateActivityIndicator isActive: Bool)
func roomContextActionService(_ service: RoomContextActionServiceProtocol, presentAlert alertController: UIAlertController)
func roomContextActionService(_ service: RoomContextActionServiceProtocol, showRoomNotificationSettingsForRoomWithId roomId: String)
func roomContextActionServiceDidJoinRoom(_ service: RoomContextActionServiceProtocol)
func roomContextActionServiceDidLeaveRoom(_ service: RoomContextActionServiceProtocol)
}
/// `RoomContextActionServiceProtocol` classes are meant to be called by a `RoomActionProviderProtocol` instance so it provides the implementation of the menu actions.
@objc protocol RoomContextActionServiceProtocol {
var delegate: RoomContextActionServiceDelegate? { get set }
var roomId: String { get }
var session: MXSession { get }
}

View file

@ -0,0 +1,90 @@
//
// 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
/// `RoomContextActionService` implements all the possible actions for a room not owned by the user (e.g. `MXPublicRoom`, `MXSpaceChildInfo`)
class UnownedRoomContextActionService: NSObject, RoomContextActionServiceProtocol {
// MARK: - RoomContextActionServiceProtocol
internal let roomId: String
internal let session: MXSession
internal weak var delegate: RoomContextActionServiceDelegate?
// MARK: - Properties
private let canonicalAlias: String?
// MARK: - Setup
init(roomId: String, canonicalAlias: String?, session: MXSession, delegate: RoomContextActionServiceDelegate?) {
self.roomId = roomId
self.canonicalAlias = canonicalAlias
self.session = session
self.delegate = delegate
}
// MARK: - Public
func joinRoom() {
self.delegate?.roomContextActionService(self, updateActivityIndicator: true)
if let canonicalAlias = canonicalAlias {
self.session.matrixRestClient.resolveRoomAlias(canonicalAlias) { [weak self] (response) in
guard let self = self else { return }
switch response {
case .success(let resolution):
self.joinRoom(withId: resolution.roomId, via: resolution.servers)
case .failure(let error):
MXLog.warning("[UnownedRoomContextActionService] joinRoom: failed to resolve room alias due to error \(error).")
self.joinRoom(withId: self.roomId, via: nil)
}
}
} else {
MXLog.warning("[UnownedRoomContextActionService] joinRoom: no canonical alias provided.")
joinRoom(withId: self.roomId, via: nil)
}
}
// MARK: - Private
private func joinRoom(withId roomId: String, via viaServers: [String]?) {
self.session.joinRoom(roomId, viaServers: viaServers, withSignUrl: nil) { [weak self] response in
guard let self = self else { return }
switch response {
case .success:
self.delegate?.roomContextActionService(self, updateActivityIndicator: false)
self.delegate?.roomContextActionServiceDidJoinRoom(self)
case .failure(let error):
self.delegate?.roomContextActionService(self, updateActivityIndicator: false)
self.delegate?.roomContextActionService(self, presentAlert: self.roomJoinFailedAlert(with: error))
}
}
}
private func roomJoinFailedAlert(with error: Error) -> UIAlertController {
var message = (error as NSError).userInfo[NSLocalizedDescriptionKey] as? String
if message == "No known servers" {
// minging kludge until https://matrix.org/jira/browse/SYN-678 is fixed
// 'Error when trying to join an empty room should be more explicit'
message = VectorL10n.roomErrorJoinFailedEmptyRoom
}
let alertController = UIAlertController(title: VectorL10n.roomErrorJoinFailedTitle, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: VectorL10n.ok, style: .default, handler: nil))
return alertController
}
}

View file

@ -0,0 +1,59 @@
//
// 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
/// `PublicRoomContextPreviewViewModel` provides the data to the `RoomContextPreviewViewController` from an instance of `MXPublicRoom`
class PublicRoomContextPreviewViewModel: RoomContextPreviewViewModelProtocol {
// MARK: - Properties
private let publicRoom: MXPublicRoom
weak var viewDelegate: RoomContextPreviewViewModelViewDelegate?
// MARK: - Setup
init(publicRoom: MXPublicRoom) {
self.publicRoom = publicRoom
}
// MARK: - RoomContextPreviewViewModelProtocol
func process(viewAction: RoomContextPreviewViewAction) {
switch viewAction {
case .loadData:
self.loadData()
}
}
// MARK: - Private
private func loadData() {
let mapper = MXRoomTypeMapper(defaultRoomType: .room)
let parameters = RoomContextPreviewLoadedParameters(
roomId: publicRoom.roomId,
roomType: mapper.roomType(from: publicRoom.roomTypeString),
displayName: publicRoom.name,
topic: publicRoom.topic,
avatarUrl: publicRoom.avatarUrl,
joinRule: .none,
membership: .unknown,
inviterId: nil,
inviter: nil,
membersCount: publicRoom.numJoinedMembers)
self.viewDelegate?.roomContextPreviewViewModel(self, didUpdateViewState: .loaded(parameters))
}
}

View file

@ -0,0 +1,91 @@
//
// 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
/// `RoomContextPreviewViewModel` provides the data to the `RoomContextPreviewViewController` from an instance of `MXRoom`
class RoomContextPreviewViewModel: RoomContextPreviewViewModelProtocol {
// MARK: - Properties
private let room: MXRoom
weak var viewDelegate: RoomContextPreviewViewModelViewDelegate?
// MARK: - Setup
init(room: MXRoom) {
self.room = room
}
// MARK: - RoomContextPreviewViewModelProtocol
func process(viewAction: RoomContextPreviewViewAction) {
switch viewAction {
case .loadData:
self.loadData()
}
}
// MARK: - Private
private func loadData() {
let parameters = RoomContextPreviewLoadedParameters(
roomId: self.room.roomId,
roomType: self.room.summary?.roomType ?? .none,
displayName: self.room.displayName,
topic: self.room.summary?.topic,
avatarUrl: self.room.summary?.avatar,
joinRule: .public,
membership: self.room.summary?.membership ?? .unknown,
inviterId: nil,
inviter: nil,
membersCount: 0)
self.viewDelegate?.roomContextPreviewViewModel(self, didUpdateViewState: .loaded(parameters))
room.state { roomState in
let membersCount = roomState?.members.joinedMembers.count ?? 0
var inviteEvent: MXEvent?
roomState?.stateEvents.forEach({ event in
guard let membership = event.wireContent["membership"] as? String, membership == "invite", event.stateKey == self.room.mxSession.myUserId else {
return
}
inviteEvent = event
})
let inviter: MXUser?
if let inviterId = inviteEvent?.sender {
inviter = self.room.mxSession.user(withUserId: inviterId)
} else {
inviter = nil
}
let parameters = RoomContextPreviewLoadedParameters(
roomId: self.room.roomId,
roomType: self.room.summary?.roomType ?? .none,
displayName: self.room.displayName,
topic: roomState?.topic,
avatarUrl: roomState?.avatar,
joinRule: roomState?.joinRule,
membership: self.room.summary?.membership ?? .unknown,
inviterId: inviteEvent?.sender,
inviter: inviter,
membersCount: membersCount)
self.viewDelegate?.roomContextPreviewViewModel(self, didUpdateViewState: .loaded(parameters))
}
}
}

View file

@ -0,0 +1,52 @@
//
// 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
/// All the data potentially loaded by the `RoomContextPreviewViewModelProtocol` to the `RoomContextPreviewViewController`
struct RoomContextPreviewLoadedParameters {
let roomId: String
let roomType: MXRoomType
let displayName: String?
let topic: String?
let avatarUrl: String?
let joinRule: MXRoomJoinRule?
let membership: MXMembership
let inviterId: String?
let inviter: MXUser?
let membersCount: Int
}
/// `RoomContextPreviewViewController` view state
enum RoomContextPreviewViewState {
case loaded(_ paremeters: RoomContextPreviewLoadedParameters)
}
/// `RoomContextPreviewViewController` view action
enum RoomContextPreviewViewAction {
case loadData
}
/// View delegate for `RoomContextPreviewViewModelProtocol`
protocol RoomContextPreviewViewModelViewDelegate: AnyObject {
func roomContextPreviewViewModel(_ viewModel: RoomContextPreviewViewModelProtocol, didUpdateViewState viewSate: RoomContextPreviewViewState)
}
/// Classes compliant with `RoomContextPreviewViewModelProtocol` are meant to provide the data to the `RoomContextPreviewViewController`
protocol RoomContextPreviewViewModelProtocol {
var viewDelegate: RoomContextPreviewViewModelViewDelegate? { get set }
func process(viewAction: RoomContextPreviewViewAction)
}

View file

@ -0,0 +1,58 @@
//
// 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
/// `SpaceChildContextPreviewViewModel` provides the data to the `RoomContextPreviewViewController` from an instance of `MXSpaceChildInfo`
class SpaceChildContextPreviewViewModel: RoomContextPreviewViewModelProtocol {
// MARK: - Properties
private let childInfo: MXSpaceChildInfo
weak var viewDelegate: RoomContextPreviewViewModelViewDelegate?
// MARK: - Setup
init(childInfo: MXSpaceChildInfo) {
self.childInfo = childInfo
}
// MARK: - RoomContextPreviewViewModelProtocol
func process(viewAction: RoomContextPreviewViewAction) {
switch viewAction {
case .loadData:
self.loadData()
}
}
// MARK: - Private
private func loadData() {
let parameters = RoomContextPreviewLoadedParameters(
roomId: childInfo.childRoomId,
roomType: childInfo.roomType,
displayName: childInfo.displayName,
topic: childInfo.topic,
avatarUrl: childInfo.avatarUrl,
joinRule: .none,
membership: .unknown,
inviterId: nil,
inviter: nil,
membersCount: childInfo.activeMemberCount)
self.viewDelegate?.roomContextPreviewViewModel(self, didUpdateViewState: .loaded(parameters))
}
}

View file

@ -150,11 +150,6 @@
threadParameters = [[ThreadParameters alloc] initWithThreadId:event.threadId
stackRoomScreen:NO];
}
else if (event.unsignedData.relations.thread || [self.mainSession.threadingService isEventThreadRoot:event])
{
threadParameters = [[ThreadParameters alloc] initWithThreadId:event.eventId
stackRoomScreen:NO];
}
}
ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:NO];

View file

@ -157,11 +157,6 @@
threadParameters = [[ThreadParameters alloc] initWithThreadId:event.threadId
stackRoomScreen:NO];
}
else if (event.unsignedData.relations.thread || [self.mainSession.threadingService isEventThreadRoot:event])
{
threadParameters = [[ThreadParameters alloc] initWithThreadId:event.eventId
stackRoomScreen:NO];
}
}
ScreenPresentationParameters *screenParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:NO stackAboveVisibleViews:NO];

View file

@ -21,7 +21,7 @@
#import "GeneratedInterface-Swift.h"
@interface DirectoryViewController ()
@interface DirectoryViewController () <RoomNotificationSettingsCoordinatorBridgePresenterDelegate, RoomContextActionServiceDelegate>
{
PublicRoomsDirectoryDataSource *dataSource;
@ -37,6 +37,10 @@
@property (nonatomic) AnalyticsScreenTracker *screenTracker;
@property (nonatomic, strong) RoomNotificationSettingsCoordinatorBridgePresenter *roomNotificationSettingsCoordinatorBridgePresenter;
@property (nonatomic, strong) PublicRoomContextMenuProvider *contextMenuProvider;
@end
@implementation DirectoryViewController
@ -45,6 +49,9 @@
{
[super finalizeInit];
self.contextMenuProvider = [PublicRoomContextMenuProvider new];
self.contextMenuProvider.serviceDelegate = self;
// Setup `MXKViewControllerHandling` properties
self.enableBarTintColorStatusChange = NO;
self.rageShakeManager = [RageShakeManager sharedManager];
@ -191,36 +198,7 @@
{
MXPublicRoom *publicRoom = [dataSource roomAtIndexPath:indexPath];
// Check whether the user has already joined the selected public room
if ([dataSource.mxSession isJoinedOnRoom:publicRoom.roomId])
{
// Open the public room.
[self showRoomWithId:publicRoom.roomId inMatrixSession:dataSource.mxSession];
}
else
{
// Preview the public room
if (publicRoom.worldReadable)
{
RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithPublicRoom:publicRoom andSession:dataSource.mxSession];
[self startActivityIndicator];
// Try to get more information about the room before opening its preview
[roomPreviewData peekInRoom:^(BOOL succeeded) {
[self stopActivityIndicator];
[self showRoomPreviewWithData:roomPreviewData];
}];
}
else
{
RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithPublicRoom:publicRoom andSession:dataSource.mxSession];
[self showRoomPreviewWithData:roomPreviewData];
}
}
[self showRoomWithPublicRoom:publicRoom];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
@ -360,4 +338,120 @@
}
}
- (void)showRoomWithPublicRoom:(MXPublicRoom*)publicRoom
{
// Check whether the user has already joined the selected public room
if ([dataSource.mxSession isJoinedOnRoom:publicRoom.roomId])
{
// Open the public room.
[self showRoomWithId:publicRoom.roomId inMatrixSession:dataSource.mxSession];
}
else
{
// Preview the public room
if (publicRoom.worldReadable)
{
RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithPublicRoom:publicRoom andSession:dataSource.mxSession];
[self startActivityIndicator];
// Try to get more information about the room before opening its preview
[roomPreviewData peekInRoom:^(BOOL succeeded) {
[self stopActivityIndicator];
[self showRoomPreviewWithData:roomPreviewData];
}];
}
else
{
RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithPublicRoom:publicRoom andSession:dataSource.mxSession];
[self showRoomPreviewWithData:roomPreviewData];
}
}
}
- (void)changeRoomNotificationSettingsForRoomWithId:(NSString*)roomId
{
MXRoom *room = [dataSource.mxSession roomWithRoomId:roomId];
if (room)
{
// navigate
self.roomNotificationSettingsCoordinatorBridgePresenter = [[RoomNotificationSettingsCoordinatorBridgePresenter alloc] initWithRoom:room];
self.roomNotificationSettingsCoordinatorBridgePresenter.delegate = self;
[self.roomNotificationSettingsCoordinatorBridgePresenter presentFrom:self animated:YES];
}
}
#pragma mark - Context Menu
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0))
{
MXPublicRoom *publicRoom = [dataSource roomAtIndexPath:indexPath];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (!publicRoom || !cell)
{
return nil;
}
return [self.contextMenuProvider contextMenuConfigurationWith:publicRoom from:cell session:dataSource.mxSession];
}
- (void)tableView:(UITableView *)tableView willPerformPreviewActionForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration animator:(id<UIContextMenuInteractionCommitAnimating>)animator API_AVAILABLE(ios(13.0))
{
MXPublicRoom *publicRoom = [self.contextMenuProvider publicRoomFrom:configuration.identifier];
if (!publicRoom)
{
return;
}
[animator addCompletion:^{
[self showRoomWithPublicRoom:publicRoom];
}];
}
#pragma mark - RoomContextActionServiceDelegate
- (void)roomContextActionServiceDidJoinRoom:(id<RoomContextActionServiceProtocol>)service
{
// Nothing to do here
}
- (void)roomContextActionServiceDidLeaveRoom:(id<RoomContextActionServiceProtocol>)service
{
// Nothing to do here
}
- (void)roomContextActionService:(id<RoomContextActionServiceProtocol>)service presentAlert:(UIAlertController *)alertController
{
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)roomContextActionService:(id<RoomContextActionServiceProtocol>)service updateActivityIndicator:(BOOL)isActive
{
if (isActive)
{
[self startActivityIndicator];
}
else
{
[self stopActivityIndicator];
}
}
- (void)roomContextActionService:(id<RoomContextActionServiceProtocol>)service showRoomNotificationSettingsForRoomWithId:(NSString *)roomId
{
[self changeRoomNotificationSettingsForRoomWithId:roomId];
}
#pragma mark - RoomNotificationSettingsCoordinatorBridgePresenterDelegate
-(void)roomNotificationSettingsCoordinatorBridgePresenterDelegateDidComplete:(RoomNotificationSettingsCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[coordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.roomNotificationSettingsCoordinatorBridgePresenter = nil;
}
@end

View file

@ -656,111 +656,6 @@
[self.recentsSearchBar resignFirstResponder];
}
- (UIContextMenuConfiguration *)collectionView:(UICollectionView *)collectionView contextMenuConfigurationForItemAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0))
{
UIView *cell = [collectionView cellForItemAtIndexPath:indexPath];
MXRoom *room = [self.dataSource getRoomAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:collectionView.tag]];
NSString *roomId = room.roomId;
MXWeakify(self);
MXWeakify(room);
return [UIContextMenuConfiguration configurationWithIdentifier:roomId previewProvider:^UIViewController * _Nullable {
// Add a preview using the cell's data to prevent the avatar and displayname from changing with a room list update.
return [[ContextMenuSnapshotPreviewViewController alloc] initWithView:cell];
} actionProvider:^UIMenu * _Nullable(NSArray<UIMenuElement *> * _Nonnull suggestedActions) {
MXStrongifyAndReturnValueIfNil(room, nil);
BOOL isDirect = room.isDirect;
UIAction *directChatAction = [UIAction actionWithTitle:isDirect ? VectorL10n.homeContextMenuMakeRoom : VectorL10n.homeContextMenuMakeDm
image:[UIImage systemImageNamed:isDirect ? @"person.crop.circle.badge.xmark" : @"person.circle"]
identifier:nil
handler:^(__kindof UIAction * _Nonnull action) {
MXStrongifyAndReturnIfNil(self);
[self updateRoomWithId:roomId asDirect:!isDirect];
}];
BOOL isMuted = room.isMute || room.isMentionsOnly;
UIImage *notificationsImage;
NSString *notificationsTitle;
if ([BuildSettings showNotificationsV2])
{
notificationsTitle = VectorL10n.homeContextMenuNotifications;
notificationsImage = [UIImage systemImageNamed:@"bell"];
}
else
{
notificationsTitle = isMuted ? VectorL10n.homeContextMenuUnmute : VectorL10n.homeContextMenuMute;
notificationsImage = [UIImage systemImageNamed:isMuted ? @"bell.slash": @"bell"];
}
UIAction *notificationsAction = [UIAction actionWithTitle:notificationsTitle
image:notificationsImage
identifier:nil
handler:^(__kindof UIAction * _Nonnull action) {
MXStrongifyAndReturnIfNil(self);
[self updateRoomWithId:roomId asMuted:!isMuted];
}];
// Get the room tag (use only the first one).
MXRoomTag* currentTag = nil;
if (room.accountData.tags)
{
NSArray<MXRoomTag*>* tags = room.accountData.tags.allValues;
if (tags.count)
{
currentTag = tags[0];
}
}
BOOL isFavourite = (currentTag && [kMXRoomTagFavourite isEqualToString:currentTag.name]);
UIAction *favouriteAction = [UIAction actionWithTitle:isFavourite ? VectorL10n.homeContextMenuUnfavourite : VectorL10n.homeContextMenuFavourite
image:[UIImage systemImageNamed:isFavourite ? @"star.slash" : @"star"]
identifier:nil
handler:^(__kindof UIAction * _Nonnull action) {
MXStrongifyAndReturnIfNil(self);
[self updateRoomWithId:roomId asFavourite:!isFavourite];
}];
BOOL isLowPriority = (currentTag && [kMXRoomTagLowPriority isEqualToString:currentTag.name]);
UIAction *lowPriorityAction = [UIAction actionWithTitle:isLowPriority ? VectorL10n.homeContextMenuNormalPriority : VectorL10n.homeContextMenuLowPriority
image:[UIImage systemImageNamed:isLowPriority ? @"arrow.up" : @"arrow.down"]
identifier:nil
handler:^(__kindof UIAction * _Nonnull action) {
MXStrongifyAndReturnIfNil(self);
[self updateRoomWithId:roomId asLowPriority:!isLowPriority];
}];
UIImage *leaveImage;
if (@available(iOS 14.0, *))
{
leaveImage = [UIImage systemImageNamed:@"rectangle.righthalf.inset.fill.arrow.right"];
}
else
{
leaveImage = [UIImage systemImageNamed:@"rectangle.xmark"];
}
UIAction *leaveAction = [UIAction actionWithTitle:VectorL10n.homeContextMenuLeave
image:leaveImage
identifier:nil
handler:^(__kindof UIAction * _Nonnull action) {
MXStrongifyAndReturnIfNil(self);
[self leaveRoomWithId:roomId];
}];
leaveAction.attributes = UIMenuElementAttributesDestructive;
return [UIMenu menuWithTitle:@"" children:@[
directChatAction,
notificationsAction,
favouriteAction,
lowPriorityAction,
leaveAction
]];
}];
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
@ -897,50 +792,6 @@
[self leaveEditedRoom];
}
// MARK: - Context Menu Actions
- (void)updateRoomWithId:(NSString *)roomId asDirect:(BOOL)direct
{
editedRoomId = roomId;
[self makeDirectEditedRoom:direct];
editedRoomId = nil;
}
- (void)updateRoomWithId:(NSString *)roomId asMuted:(BOOL)muted
{
editedRoomId = roomId;
if ([BuildSettings showNotificationsV2])
{
[self changeEditedRoomNotificationSettings];
}
else
{
[self muteEditedRoomNotifications:muted];
}
editedRoomId = nil;
}
- (void)updateRoomWithId:(NSString *)roomId asFavourite:(BOOL)favourite
{
editedRoomId = roomId;
[self updateEditedRoomTag:favourite ? kMXRoomTagFavourite : nil];
editedRoomId = nil;
}
- (void)updateRoomWithId:(NSString *)roomId asLowPriority:(BOOL)lowPriority
{
editedRoomId = roomId;
[self updateEditedRoomTag:lowPriority ? kMXRoomTagLowPriority : nil];
editedRoomId = nil;
}
- (void)leaveRoomWithId:(NSString *)roomId
{
editedRoomId = roomId;
[self leaveEditedRoom];
editedRoomId = nil;
}
#pragma mark - SecureBackupSetupCoordinatorBridgePresenterDelegate
- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidComplete:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter
@ -1069,4 +920,47 @@
}];
}
#pragma mark - Context Menu
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0))
{
return nil;
}
- (UIContextMenuConfiguration *)collectionView:(UICollectionView *)collectionView contextMenuConfigurationForItemAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0))
{
id<MXKRecentCellDataStoring> cellData = [recentsDataSource cellDataAtIndexPath:[NSIndexPath indexPathForRow:indexPath.item inSection:collectionView.tag]];
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
if (!cellData || !cell)
{
return nil;
}
self.recentsUpdateEnabled = NO;
return [self.contextMenuProvider contextMenuConfigurationWith:cellData from:cell session:self.dataSource.mxSession];
}
- (void)collectionView:(UICollectionView *)collectionView willPerformPreviewActionForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration animator:(id<UIContextMenuInteractionCommitAnimating>)animator API_AVAILABLE(ios(13.0))
{
NSString *roomId = [self.contextMenuProvider roomIdFrom:configuration.identifier];
if (!roomId)
{
self.recentsUpdateEnabled = YES;
return;
}
[animator addCompletion:^{
self.recentsUpdateEnabled = YES;
[self showRoomWithRoomId:roomId inMatrixSession:self.mainSession];
}];
}
- (UITargetedPreview *)collectionView:(UICollectionView *)collectionView previewForDismissingContextMenuWithConfiguration:(UIContextMenuConfiguration *)configuration API_AVAILABLE(ios(13.0))
{
self.recentsUpdateEnabled = YES;
return nil;
}
@end

View file

@ -17,6 +17,7 @@
#import "MatrixKit.h"
@class BadgeLabel;
@class PresenceIndicatorView;
/**
'RoomCollectionViewCell' class is used to display a room in a collection view.
@ -38,6 +39,7 @@
@property (weak, nonatomic) IBOutlet MXKImageView *roomAvatar;
@property (weak, nonatomic) IBOutlet UIImageView *encryptedRoomIcon;
@property (weak, nonatomic) IBOutlet PresenceIndicatorView *presenceIndicatorView;
@property (weak, nonatomic) IBOutlet BadgeLabel *badgeLabel;

View file

@ -63,6 +63,7 @@
self.roomTitle.textColor = ThemeService.shared.theme.textPrimaryColor;
self.roomTitle1.textColor = ThemeService.shared.theme.textPrimaryColor;
self.roomTitle2.textColor = ThemeService.shared.theme.textPrimaryColor;
self.presenceIndicatorView.borderColor = ThemeService.shared.theme.backgroundColor;
self.editionArrowView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor;
@ -145,6 +146,10 @@
roomId:roomCellData.roomIdentifier
displayName:roomCellData.roomDisplayname
mediaManager:roomCellData.mxSession.mediaManager];
// Presence indicator
self.presenceIndicatorView.presence = roomCellData.presence;
self.presenceIndicatorView.hidden = roomCellData.presence == MXPresenceUnknown;
}
}

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -37,6 +37,19 @@
<constraint firstAttribute="width" secondItem="5Yd-df-HbB" secondAttribute="height" multiplier="1:1" constant="1" id="QGp-OT-7yA"/>
</constraints>
</imageView>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JjC-wU-vgp" customClass="PresenceIndicatorView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="52" y="52" width="18" height="18"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="18" id="B0R-l8-aEh"/>
<constraint firstAttribute="height" constant="18" id="VaP-nI-eyF"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
<real key="value" value="1.5"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="RoomTitle" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oxX-IL-dG4">
<rect key="frame" x="4" y="74" width="72" height="16"/>
<accessibility key="accessibilityConfiguration" identifier="TitleLabel"/>
@ -81,11 +94,13 @@
<constraint firstItem="Jkz-Zp-aaG" firstAttribute="top" secondItem="X8H-1U-wc3" secondAttribute="bottom" id="XDO-yX-Zs5"/>
<constraint firstItem="X8H-1U-wc3" firstAttribute="leading" secondItem="eCk-zY-LXq" secondAttribute="leading" constant="7" id="XU5-Lv-9Xn"/>
<constraint firstItem="T1Q-RS-8o6" firstAttribute="top" secondItem="eCk-zY-LXq" secondAttribute="top" constant="10" id="cc7-bg-15Z"/>
<constraint firstItem="JjC-wU-vgp" firstAttribute="bottom" secondItem="T1Q-RS-8o6" secondAttribute="bottom" id="epD-El-6ls"/>
<constraint firstItem="Jkz-Zp-aaG" firstAttribute="leading" secondItem="eCk-zY-LXq" secondAttribute="leading" constant="7" id="fPh-Lb-Bv9"/>
<constraint firstItem="scs-Ov-Tjv" firstAttribute="centerX" secondItem="T1Q-RS-8o6" secondAttribute="trailing" priority="750" constant="-6" id="fgA-Sf-Y4E"/>
<constraint firstAttribute="trailing" secondItem="oxX-IL-dG4" secondAttribute="trailing" constant="4" id="hDl-X9-M4n"/>
<constraint firstItem="T1Q-RS-8o6" firstAttribute="centerX" secondItem="eCk-zY-LXq" secondAttribute="centerX" id="hmB-fl-oN2"/>
<constraint firstAttribute="trailing" secondItem="X8H-1U-wc3" secondAttribute="trailing" constant="7" id="o5i-7H-n0G"/>
<constraint firstItem="JjC-wU-vgp" firstAttribute="trailing" secondItem="T1Q-RS-8o6" secondAttribute="trailing" id="oSg-8N-1F7"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="scs-Ov-Tjv" secondAttribute="trailing" id="qGg-o0-u1H"/>
<constraint firstAttribute="trailing" secondItem="Jkz-Zp-aaG" secondAttribute="trailing" constant="7" id="uQe-FG-3lb"/>
<constraint firstItem="X8H-1U-wc3" firstAttribute="top" secondItem="oxX-IL-dG4" secondAttribute="top" id="uTm-3W-QoM"/>
@ -95,6 +110,7 @@
<outlet property="badgeLabel" destination="scs-Ov-Tjv" id="KPV-Qb-cnT"/>
<outlet property="editionArrowView" destination="jta-3V-4wL" id="XLj-Cx-3bn"/>
<outlet property="encryptedRoomIcon" destination="5Yd-df-HbB" id="5pc-Vi-wMZ"/>
<outlet property="presenceIndicatorView" destination="JjC-wU-vgp" id="PfX-eJ-ZWm"/>
<outlet property="roomAvatar" destination="T1Q-RS-8o6" id="4sR-Wm-jwz"/>
<outlet property="roomTitle" destination="oxX-IL-dG4" id="vff-Fw-AOf"/>
<outlet property="roomTitle1" destination="X8H-1U-wc3" id="nnS-Hp-1qV"/>

View file

@ -15,6 +15,7 @@
*/
#import "NSBundle+MXKLanguage.h"
#import "GeneratedInterface-Swift.h"
#import <objc/runtime.h>
@ -55,37 +56,37 @@ static const char _fallbackLanguage = 0;
[self setupMXKLanguageBundle];
// [NSBundle localizedStringForKey] calls will be redirected to the bundle corresponding
// to "language"
objc_setAssociatedObject([NSBundle mainBundle],
&_bundle, language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil,
// to "language". `lprojBundleFor` loads this from the main app bundle as we might be running in an extension.
objc_setAssociatedObject(NSBundle.app,
&_bundle, language ? [NSBundle lprojBundleFor:language] : nil,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject([NSBundle mainBundle],
objc_setAssociatedObject(NSBundle.app,
&_language, language,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+ (NSString *)mxk_language
{
return objc_getAssociatedObject([NSBundle mainBundle], &_language);
return objc_getAssociatedObject(NSBundle.app, &_language);
}
+ (void)mxk_setFallbackLanguage:(NSString *)language
{
[self setupMXKLanguageBundle];
objc_setAssociatedObject([NSBundle mainBundle],
&_fallbackBundle, language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil,
objc_setAssociatedObject(NSBundle.app,
&_fallbackBundle, language ? [NSBundle lprojBundleFor:language] : nil,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject([NSBundle mainBundle],
objc_setAssociatedObject(NSBundle.app,
&_fallbackLanguage, language,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
+ (NSString *)mxk_fallbackLanguage
{
return objc_getAssociatedObject([NSBundle mainBundle], &_fallbackLanguage);
return objc_getAssociatedObject(NSBundle.app, &_fallbackLanguage);
}
#pragma mark - Private methods
@ -96,7 +97,7 @@ static const char _fallbackLanguage = 0;
dispatch_once(&onceToken, ^{
// Use MXKLanguageBundle as the [NSBundle mainBundle] class
object_setClass([NSBundle mainBundle], [MXKLanguageBundle class]);
object_setClass(NSBundle.app, MXKLanguageBundle.class);
});
}

View file

@ -59,6 +59,12 @@ limitations under the License.
The fake top view displayed in case of vertical bounce.
*/
__weak UIView *topview;
/**
`isRefreshNeeded` is set to `YES` if an update of the datasource has been triggered but the UI has not been updated.
It's set to `NO` after a refresh of the UI.
*/
BOOL isRefreshNeeded;
}
@property (weak, nonatomic) IBOutlet UISearchBar *recentsSearchBar;
@ -83,6 +89,11 @@ limitations under the License.
*/
@property (nonatomic) BOOL enableBarButtonSearch;
/**
Enabled or disabled the UI update after recents syncs. Default YES.
*/
@property (nonatomic, getter=isRecentsUpdateEnabled) BOOL recentsUpdateEnabled;
#pragma mark - Class methods
/**

View file

@ -83,6 +83,7 @@
{
[super finalizeInit];
_recentsUpdateEnabled = YES;
_enableBarButtonSearch = YES;
}
@ -169,6 +170,8 @@
// Observe the server sync
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onSyncNotification) name:kMXSessionDidSyncNotification object:nil];
self.recentsUpdateEnabled = YES;
}
- (void)viewWillDisappear:(BOOL)animated
@ -319,6 +322,10 @@
- (void)refreshRecentsTable
{
if (!self.recentsUpdateEnabled) return;
isRefreshNeeded = NO;
// For now, do a simple full reload
[self.recentsTableView reloadData];
}
@ -330,6 +337,16 @@
[self.view setNeedsUpdateConstraints];
}
- (void)setRecentsUpdateEnabled:(BOOL)activeUpdate
{
_recentsUpdateEnabled = activeUpdate;
if (_recentsUpdateEnabled && isRefreshNeeded)
{
[self refreshRecentsTable];
}
}
#pragma mark - Action
- (IBAction)search:(id)sender
@ -385,6 +402,12 @@
- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes
{
if (!_recentsUpdateEnabled)
{
isRefreshNeeded = YES;
return;
}
// For now, do a simple full reload
[self refreshRecentsTable];
}

View file

@ -715,6 +715,7 @@ static NSArray<NSNumber*> *initialSyncSilentErrorsHTTPStatusCodes;
// Instantiate new session
mxSession = [[MXSession alloc] initWithMatrixRestClient:mxRestClient];
mxSession.preferredSyncPresence = self.preferredSyncPresence;
// Check whether an antivirus url is defined.
if (_antivirusServerURL)
@ -1007,7 +1008,7 @@ static NSArray<NSNumber*> *initialSyncSilentErrorsHTTPStatusCodes;
// Update user presence
MXWeakify(self);
[self setUserPresence:MXPresenceUnavailable andStatusMessage:nil completion:^{
[self setUserPresence:MXPresenceOffline andStatusMessage:nil completion:^{
MXStrongifyAndReturnIfNil(self);
[self cancelPauseBackgroundTask];
}];
@ -1045,8 +1046,10 @@ static NSArray<NSNumber*> *initialSyncSilentErrorsHTTPStatusCodes;
case MXSessionStatePauseRequested:
{
// Resume SDK and update user presence
MXWeakify(self);
[mxSession resume:^{
[self setUserPresence:MXPresenceOnline andStatusMessage:nil completion:nil];
MXStrongifyAndReturnIfNil(self);
[self setUserPresence:self.preferredSyncPresence andStatusMessage:nil completion:nil];
[self refreshAPNSPusher];
[self refreshPushKitPusher];
@ -1513,7 +1516,7 @@ static NSArray<NSNumber*> *initialSyncSilentErrorsHTTPStatusCodes;
MXLogDebug(@"[MXKAccount] %@: The session is ready. Matrix SDK session has been started in %0.fms.", self.mxCredentials.userId, [[NSDate date] timeIntervalSinceDate:self->openSessionStartDate] * 1000);
[self setUserPresence:MXPresenceOnline andStatusMessage:nil completion:nil];
[self setUserPresence:self.preferredSyncPresence andStatusMessage:nil completion:nil];
} failure:^(NSError *error) {
MXStrongifyAndReturnIfNil(self);
@ -2159,4 +2162,20 @@ static NSArray<NSNumber*> *initialSyncSilentErrorsHTTPStatusCodes;
}
}
#pragma mark - Presence
- (void)setPreferredSyncPresence:(MXPresence)preferredSyncPresence
{
[super setPreferredSyncPresence:preferredSyncPresence];
if (self.mxSession)
{
self.mxSession.preferredSyncPresence = preferredSyncPresence;
[self setUserPresence:preferredSyncPresence andStatusMessage:nil completion:nil];
}
// Archive updated field
[[MXKAccountManager sharedManager] saveAccounts];
}
@end

View file

@ -32,6 +32,7 @@
@protected BOOL _isSoftLogout;
@protected BOOL _hasPusherForPushNotifications;
@protected BOOL _hasPusherForPushKitNotifications;
@protected MXPresence _preferredSyncPresence;
}
/**
@ -89,6 +90,12 @@
*/
@property (nonatomic, readonly) BOOL hasPusherForPushKitNotifications;
/**
The account's preferred Presence status to share while the application is in foreground.
Defaults to MXPresenceOnline.
*/
@property (nonatomic) MXPresence preferredSyncPresence;
/**
Enable In-App notifications based on Remote notifications rules.

View file

@ -80,6 +80,16 @@
_warnedAboutEncryption = [coder decodeBoolForKey:@"warnedAboutEncryption"];
if ([coder decodeObjectOfClass:NSString.class forKey:@"preferredSyncPresence"])
{
MXPresenceString presenceString = [coder decodeObjectOfClass:NSString.class forKey:@"preferredSyncPresence"];
_preferredSyncPresence = [MXTools presence:presenceString];
}
else
{
_preferredSyncPresence = MXPresenceOnline;
}
_others = [coder decodeObjectForKey:@"others"];
}
@ -143,6 +153,9 @@
[coder encodeBool:_warnedAboutEncryption forKey:@"warnedAboutEncryption"];
MXPresenceString presenceString = [MXTools presenceString:_preferredSyncPresence];
[coder encodeObject:presenceString forKey:@"preferredSyncPresence"];
[coder encodeObject:_others forKey:@"others"];
}

View file

@ -21,6 +21,7 @@
#import "MXKDataSource.h"
#import "MXKRoomBubbleCellDataStoring.h"
#import "MXKEventFormatter.h"
#import "MXEventContentLocation.h"
@class MXKQueuedEvent;
@ -352,10 +353,17 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey;
- (void)limitMemoryUsage:(NSInteger)maxBubbleNb;
/**
Force data reload.
Force data reload. Calls `reloadNotifying` with `YES`.
*/
- (void)reload;
/**
Force data reload.
@param notify Flag to notify the delegate about the changes.
*/
- (void)reloadNotifying:(BOOL)notify;
/**
Called when room property changed. Designed to be used by subclasses.
*/
@ -614,6 +622,7 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey;
@param latitude the location's latitude
@param longitude the location's longitude
@param description an optional description
@param assetType the location's type
@param success A block object called when the operation succeeds. It returns
the event id of the event generated on the homeserver
@param failure A block object called when the operation fails.
@ -621,6 +630,7 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey;
- (void)sendLocationWithLatitude:(double)latitude
longitude:(double)longitude
description:(NSString *)description
coordinateType:(MXEventAssetType)coordinateType
success:(void (^)(NSString *))success
failure:(void (^)(NSError *))failure;

View file

@ -2104,6 +2104,7 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
- (void)sendLocationWithLatitude:(double)latitude
longitude:(double)longitude
description:(NSString *)description
coordinateType:(MXEventAssetType)coordinateType
success:(void (^)(NSString *))success
failure:(void (^)(NSError *))failure
{
@ -2115,7 +2116,7 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) {
description:description
threadId:self.threadId
localEcho:&localEchoEvent
assetType:MXEventAssetTypeGeneric
assetType:coordinateType
success:success failure:failure];
if (localEchoEvent)

View file

@ -93,6 +93,19 @@
return roomSummary.avatar;
}
- (MXPresence)presence
{
if (self.roomSummary.isDirect)
{
MXUser *contact = [self.mxSession userWithUserId:self.roomSummary.directUserId];
return contact.presence;
}
else
{
return MXPresenceUnknown;
}
}
- (NSString *)lastEventTextMessage
{
if (self.isSuggestedRoom)

View file

@ -44,6 +44,7 @@
@property (nonatomic, readonly) NSString *roomIdentifier;
@property (nonatomic, readonly) NSString *roomDisplayname;
@property (nonatomic, readonly) NSString *avatarUrl;
@property (nonatomic, readonly) MXPresence presence;
@property (nonatomic, readonly) NSString *lastEventTextMessage;
@property (nonatomic, readonly) NSString *lastEventDate;

View file

@ -142,7 +142,9 @@ NSString *const kMXKContactCellContactIdKey = @"kMXKContactCellContactIdKey";
if (! self.hideMatrixPresence)
{
// Observe contact presence change
MXWeakify(self);
mxPresenceObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKContactManagerMatrixUserPresenceChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
MXStrongifyAndReturnIfNil(self);
// get the matrix identifiers
NSArray* matrixIdentifiers = self->contact.matrixIdentifiers;

View file

@ -64,6 +64,8 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
private var useCaseResult: OnboardingUseCaseViewModelResult?
private var authenticationType: MXKAuthenticationType?
private var session: MXSession?
/// A place to store the image selected in the avatar screen until it has been saved.
private var selectedAvatar: UIImage?
private var shouldShowDisplayNameScreen = false
private var shouldShowAvatarScreen = false
@ -255,10 +257,15 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
if #available(iOS 14.0, *) {
if authenticationType == .register,
let userId = session.credentials.userId,
let userSession = UserSessionsService.shared.userSession(withUserId: userId),
BuildSettings.onboardingShowAccountPersonalization {
let userSession = UserSessionsService.shared.userSession(withUserId: userId) {
// If personalisation is to be shown, check that the homeserver supports it otherwise show the congratulations screen
if BuildSettings.onboardingShowAccountPersonalization {
checkHomeserverCapabilities(for: userSession)
return
} else {
showCongratulationsScreen(for: userSession)
return
}
} else if Analytics.shared.shouldShowAnalyticsPrompt {
showAnalyticsPrompt(for: session)
return
@ -373,9 +380,9 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
let parameters = OnboardingDisplayNameCoordinatorParameters(userSession: userSession)
let coordinator = OnboardingDisplayNameCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] session in
coordinator.completion = { [weak self, weak coordinator] userSession in
guard let self = self, let coordinator = coordinator else { return }
self.displayNameCoordinator(coordinator, didCompleteWith: session)
self.displayNameCoordinator(coordinator, didCompleteWith: userSession)
}
add(childCoordinator: coordinator)
@ -401,12 +408,20 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
private func showAvatarScreen(for userSession: UserSession) {
MXLog.debug("[OnboardingCoordinator]: showAvatarScreen")
let parameters = OnboardingAvatarCoordinatorParameters(userSession: userSession)
let parameters = OnboardingAvatarCoordinatorParameters(userSession: userSession, avatar: selectedAvatar)
let coordinator = OnboardingAvatarCoordinator(parameters: parameters)
coordinator.completion = { [weak self, weak coordinator] session in
coordinator.completion = { [weak self, weak coordinator] result in
guard let self = self, let coordinator = coordinator else { return }
self.avatarCoordinator(coordinator, didCompleteWith: session)
switch result {
case .selectedAvatar(let image):
// Store the avatar so that if the user navigates back to the display name
// screen we can show the chosen image again when the avatar screen is pushed.
self.selectedAvatar = image
case .complete(let userSession):
self.avatarCoordinator(coordinator, didCompleteWith: userSession)
}
}
add(childCoordinator: coordinator)
@ -427,6 +442,9 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
@available(iOS 14.0, *)
private func avatarCoordinator(_ coordinator: OnboardingAvatarCoordinator, didCompleteWith userSession: UserSession) {
showCelebrationScreen(for: userSession)
// It is no longer possible to navigate backwards so forget the selected avatar
selectedAvatar = nil
}
@available(iOS 14.0, *)

View file

@ -973,6 +973,13 @@ const CGFloat kTypingCellHeight = 24;
// no need to reload when paginating back
return;
}
BOOL notify = YES;
if (self.threadId)
{
// no need to notify the thread screen, it'll cause a flickering
notify = NO;
}
NSUInteger count = 0;
@synchronized (bubbles)
{
@ -980,7 +987,7 @@ const CGFloat kTypingCellHeight = 24;
}
if (count > 0)
{
[self reload];
[self reloadNotifying:notify];
}
}

View file

@ -16,6 +16,7 @@
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="location_user_marker" translatesAutoresizingMaskIntoConstraints="NO" id="ldO-kc-R5W">
<rect key="frame" x="0.0" y="0.0" width="50" height="54"/>
<color key="tintColor" red="0.050980392159999999" green="0.74117647060000003" blue="0.5450980392" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="width" constant="50" id="41S-fj-tn4"/>
<constraint firstAttribute="height" constant="54" id="MAX-5E-xvS"/>

Some files were not shown because too many files have changed in this diff Show more