Merge pull request #5987 from vector-im/release/1.8.11/release
Release 1.8.11
@@ -1,3 +1,59 @@
|
||||
## 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
|
||||
|
||||
@@ -68,7 +68,8 @@ public class UserIndicator {
|
||||
|
||||
/// Cancel the indicator, triggering any dismissal action / animation
|
||||
///
|
||||
/// Note: clients can call this method directly, if they have access to the `UserIndicator`.
|
||||
/// Note: clients can call this method directly, if they have access to the `UserIndicator`. Alternatively
|
||||
/// deallocating the `UserIndicator` will call `cancel` automatically.
|
||||
/// Once cancelled, `UserIndicatorQueue` will automatically start the next `UserIndicator` in the queue.
|
||||
public func cancel() {
|
||||
complete()
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
//
|
||||
|
||||
// Version
|
||||
MARKETING_VERSION = 1.8.10
|
||||
CURRENT_PROJECT_VERSION = 1.8.10
|
||||
MARKETING_VERSION = 1.8.11
|
||||
CURRENT_PROJECT_VERSION = 1.8.11
|
||||
|
||||
@@ -90,6 +90,12 @@ final class BuildSettings: NSObject {
|
||||
static let applicationWebAppUrlString = "https://app.element.io"
|
||||
|
||||
|
||||
// MARK: - Localization
|
||||
|
||||
/// Whether to allow the app to use a right to left layout or force left to right for all languages
|
||||
static let disableRightToLeftLayout = true
|
||||
|
||||
|
||||
// MARK: - Server configuration
|
||||
|
||||
// Default servers proposed on the authentication screen
|
||||
@@ -191,6 +197,7 @@ final class BuildSettings: NSObject {
|
||||
static let bugReportEndpointUrlString = "https://riot.im/bugreports"
|
||||
// Use the name allocated by the bug report server
|
||||
static let bugReportApplicationId = "riot-ios"
|
||||
static let bugReportUISIId = "element-auto-uisi"
|
||||
|
||||
|
||||
// MARK: - Integrations
|
||||
@@ -204,8 +211,10 @@ final class BuildSettings: NSObject {
|
||||
"https://scalar-staging.vector.im/api",
|
||||
"https://scalar-staging.riot.im/scalar/api",
|
||||
]
|
||||
// Jitsi server used outside integrations to create conference calls from the call button in the timeline
|
||||
static let jitsiServerUrl: URL = URL(string: "https://jitsi.riot.im")!
|
||||
// Jitsi server used outside integrations to create conference calls from the call button in the timeline.
|
||||
// Setting this to nil effectively disables Jitsi conference calls (given that there is no wellknown override).
|
||||
// Note: this will not remove the conference call button, use roomScreenAllowVoIPForNonDirectRoom setting.
|
||||
static let jitsiServerUrl: URL? = URL(string: "https://jitsi.riot.im")
|
||||
|
||||
|
||||
// MARK: - Features
|
||||
@@ -287,7 +296,11 @@ final class BuildSettings: NSObject {
|
||||
static let settingsSecurityScreenShowAdvancedUnverifiedDevices:Bool = true
|
||||
|
||||
// MARK: - Timeline settings
|
||||
static let roomInputToolbarCompressionMode = MXKRoomInputToolbarCompressionModePrompt
|
||||
static let roomInputToolbarCompressionMode: MediaCompressionMode = .prompt
|
||||
|
||||
enum MediaCompressionMode {
|
||||
case prompt, small, medium, large, none
|
||||
}
|
||||
|
||||
// MARK: - Room Creation Screen
|
||||
|
||||
@@ -377,6 +390,9 @@ final class BuildSettings: NSObject {
|
||||
// MARK: - Secrets Recovery
|
||||
static let secretsRecoveryAllowReset = true
|
||||
|
||||
// MARK: - UISI Autoreporting
|
||||
static let cryptoUISIAutoReportingEnabled = false
|
||||
|
||||
// MARK: - Polls
|
||||
|
||||
static var pollsEnabled: Bool {
|
||||
|
||||
@@ -67,5 +67,4 @@ public protocol Colors {
|
||||
/// - Names in chat timeline
|
||||
/// - Avatars default states that include first name letter
|
||||
var namesAndAvatars: [ColorType] { get }
|
||||
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ import UIKit
|
||||
public let background: UIColor
|
||||
|
||||
public let namesAndAvatars: [UIColor]
|
||||
|
||||
|
||||
init(values: ColorValues) {
|
||||
accent = values.accent
|
||||
alert = values.alert
|
||||
|
||||
@@ -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' } }
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_celebration_icon.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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,70ZM34.863,42.213L37.589,50.392C37.725,50.801 38.107,51.076 38.538,51.076C38.968,51.076 39.35,50.801 39.487,50.392L42.213,42.213C42.213,42.213 50.392,39.487 50.392,39.487C50.8,39.351 51.076,38.969 51.076,38.538C51.076,38.108 50.8,37.726 50.392,37.589L42.213,34.863C42.213,34.863 39.487,26.684 39.487,26.684C39.35,26.276 38.968,26 38.538,26C38.107,26 37.725,26.276 37.589,26.684L34.863,34.863C34.863,34.863 26.684,37.589 26.684,37.589C26.275,37.726 26,38.108 26,38.538C26,38.969 26.275,39.351 26.684,39.487L34.863,42.213ZM19.882,23L18.106,26.553C17.913,26.938 17.989,27.403 18.293,27.707C18.597,28.011 19.062,28.087 19.447,27.894L23,26.118C23,26.118 26.553,27.894 26.553,27.894C26.938,28.087 27.403,28.011 27.707,27.707C28.011,27.403 28.087,26.938 27.894,26.553L26.118,23C26.118,23 27.894,19.447 27.894,19.447C28.087,19.062 28.011,18.597 27.707,18.293C27.403,17.989 26.938,17.913 26.553,18.106L23,19.882C23,19.882 19.447,18.106 19.447,18.106C19.062,17.913 18.597,17.989 18.293,18.293C17.989,18.597 17.913,19.062 18.106,19.447L19.882,23Z" style="fill:rgb(13,189,139);"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 733 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "location_live_icon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "location_live_icon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "location_live_icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "location_pin_icon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "location_pin_icon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "location_pin_icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
@@ -19,5 +19,8 @@
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
"onboarding_avatar_message" = "You can change this anytime.";
|
||||
"onboarding_avatar_accessibility_label" = "Profile picture";
|
||||
|
||||
"onboarding_celebration_title" = "You’re all set!";
|
||||
"onboarding_celebration_message" = "Your preferences have been saved.";
|
||||
"onboarding_celebration_button" = "Let's go";
|
||||
|
||||
"image_picker_action_files" = "Choose from files";
|
||||
|
||||
"spaces_feature_not_available" = "This feature isn't available here. For now, you can do this with %@ on your computer.";
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"send_to" = "Send to %@";
|
||||
"close" = "Close";
|
||||
"skip" = "Skip";
|
||||
"joining" = "Joining";
|
||||
"joined" = "Joined";
|
||||
"switch" = "Switch";
|
||||
"more" = "More";
|
||||
@@ -475,6 +476,14 @@ Tap the + to start adding people.";
|
||||
"threads_empty_tip" = "Tip: Tap a message and use “Thread” to start one.";
|
||||
"threads_empty_show_all_threads" = "Show all threads";
|
||||
"message_from_a_thread" = "From a thread";
|
||||
"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";
|
||||
@@ -634,6 +643,7 @@ Tap the + to start adding people.";
|
||||
"settings_labs_enable_ringing_for_group_calls" = "Ring for group calls";
|
||||
"settings_labs_enabled_polls" = "Polls";
|
||||
"settings_labs_enable_threads" = "Threaded messaging";
|
||||
"settings_labs_enable_auto_report_decryption_errors" = "Auto Report Decryption Errors";
|
||||
"settings_labs_use_only_latest_user_avatar_and_name" = "Show latest avatar and name for users in message history";
|
||||
|
||||
"settings_version" = "Version %@";
|
||||
@@ -1028,6 +1038,7 @@ Tap the + to start adding people.";
|
||||
"call_incoming_video" = "Incoming video call…";
|
||||
"call_already_displayed" = "There is already a call in progress.";
|
||||
"call_jitsi_error" = "Failed to join the conference call.";
|
||||
"call_jitsi_unable_to_start" = "Unable to start conference call";
|
||||
|
||||
"call_no_stun_server_error_title" ="Call failed due to misconfigured server";
|
||||
"call_no_stun_server_error_message_1" ="Please ask the administrator of your homeserver %@ to configure a TURN server in order for calls to work reliably.";
|
||||
@@ -1764,6 +1775,7 @@ Tap the + to start adding people.";
|
||||
"create_room_placeholder_address" = "#testroom:matrix.org";
|
||||
"create_room_suggest_room" = "Suggest to space members";
|
||||
"create_room_suggest_room_footer" = "Suggested rooms are promoted to space members as good to join.";
|
||||
"create_room_processing" = "Creating room";
|
||||
|
||||
// MARK: - Room Info
|
||||
|
||||
@@ -2067,8 +2079,6 @@ Tap the + to start adding people.";
|
||||
|
||||
"location_sharing_close_action" = "Close";
|
||||
|
||||
"location_sharing_share_action" = "Share";
|
||||
|
||||
"location_sharing_post_failure_title" = "We couldn’t send your location";
|
||||
|
||||
"location_sharing_post_failure_subtitle" = "%@ could not send your location. Please try again later.";
|
||||
@@ -2095,9 +2105,11 @@ Tap the + to start adding people.";
|
||||
|
||||
// MARK: Live location sharing
|
||||
|
||||
"location_sharing_live_share_title" = "Share live location";
|
||||
"live_location_sharing_banner_title" = "Live location enabled";
|
||||
"live_location_sharing_banner_stop" = "Stop";
|
||||
|
||||
"location_sharing_static_share_title" = "Send my current location";
|
||||
"location_sharing_pin_drop_share_title" = "Send this location";
|
||||
|
||||
// MARK: - MatrixKit
|
||||
|
||||
@@ -2434,7 +2446,7 @@ Tap the + to start adding people.";
|
||||
"language_picker_title" = "Choose a language";
|
||||
"language_picker_default_language" = "Default (%@)";
|
||||
|
||||
/* -*-
|
||||
/* -*-
|
||||
Automatic localization for en
|
||||
|
||||
The following key/value pairs were extracted from the android i18n file:
|
||||
@@ -2517,17 +2529,17 @@ Tap the + to start adding people.";
|
||||
"notice_room_history_visible_to_members_from_joined_point_by_you" = "You made future room history visible to all room members, from the point they joined.";
|
||||
"notice_room_history_visible_to_members_from_joined_point_by_you_for_dm" = "You made future messages visible to everyone, from when they joined.";
|
||||
|
||||
// Room Screen
|
||||
// Room Screen
|
||||
|
||||
// general errors
|
||||
// general errors
|
||||
|
||||
// Home Screen
|
||||
// Home Screen
|
||||
|
||||
// Last seen time
|
||||
// Last seen time
|
||||
|
||||
// call events
|
||||
// call events
|
||||
|
||||
/* -*-
|
||||
/* -*-
|
||||
Automatic localization for en
|
||||
|
||||
The following key/value pairs were extracted from the android i18n file:
|
||||
@@ -2535,9 +2547,9 @@ Tap the + to start adding people.";
|
||||
*/
|
||||
|
||||
|
||||
// titles
|
||||
// titles
|
||||
|
||||
// button names
|
||||
// button names
|
||||
"send" = "Send";
|
||||
"copy_button_name" = "Copy";
|
||||
"resend" = "Resend";
|
||||
@@ -2545,7 +2557,7 @@ Tap the + to start adding people.";
|
||||
"share" = "Share";
|
||||
"delete" = "Delete";
|
||||
|
||||
// actions
|
||||
// actions
|
||||
"action_logout" = "Logout";
|
||||
"create_room" = "Create Room";
|
||||
"login" = "Login";
|
||||
@@ -2560,32 +2572,32 @@ Tap the + to start adding people.";
|
||||
"unban" = "Un-ban";
|
||||
"message_unsaved_changes" = "There are unsaved changes. Leaving will discard them.";
|
||||
|
||||
// Login Screen
|
||||
// Login Screen
|
||||
"login_error_already_logged_in" = "Already logged in";
|
||||
"login_error_must_start_http" = "URL must start with http[s]://";
|
||||
|
||||
// members list Screen
|
||||
// members list Screen
|
||||
|
||||
// accounts list Screen
|
||||
// accounts list Screen
|
||||
|
||||
// image size selection
|
||||
// image size selection
|
||||
|
||||
// invitation members list Screen
|
||||
// invitation members list Screen
|
||||
|
||||
// room creation dialog Screen
|
||||
// room creation dialog Screen
|
||||
|
||||
// room info dialog Screen
|
||||
|
||||
// room details dialog screen
|
||||
|
||||
// contacts list screen
|
||||
// contacts list screen
|
||||
"invitation_message" = "I\'d like to chat with you with matrix. Please, visit the website http://matrix.org to have more information.";
|
||||
|
||||
// Settings screen
|
||||
// Settings screen
|
||||
"settings_title_config" = "Configuration";
|
||||
"settings_title_notifications" = "Notifications";
|
||||
|
||||
// Notification settings screen
|
||||
// Notification settings screen
|
||||
"notification_settings_disable_all" = "Disable all notifications";
|
||||
"notification_settings_enable_notifications" = "Enable notifications";
|
||||
"notification_settings_enable_notifications_warning" = "All notifications are currently disabled for all devices.";
|
||||
@@ -2612,10 +2624,10 @@ Tap the + to start adding people.";
|
||||
"notification_settings_by_default" = "By default...";
|
||||
"notification_settings_notify_all_other" = "Notify for all other messages/rooms";
|
||||
|
||||
// gcm section
|
||||
// gcm section
|
||||
"settings_config_identity_server" = "Identity server: %@";
|
||||
|
||||
// Settings keys
|
||||
// Settings keys
|
||||
|
||||
// call string
|
||||
"call_connecting" = "Connecting…";
|
||||
@@ -2648,4 +2660,3 @@ Tap the + to start adding people.";
|
||||
"ssl_unexpected_existing_expl" = "The certificate has changed from one that was trusted by your phone. This is HIGHLY UNUSUAL. It is recommended that you DO NOT ACCEPT this new certificate.";
|
||||
"ssl_expected_existing_expl" = "The certificate has changed from a previously trusted one to one that is not trusted. The server may have renewed its certificate. Contact the server administrator for the expected fingerprint.";
|
||||
"ssl_only_accept" = "ONLY accept the certificate if the server administrator has published a fingerprint that matches the one above.";
|
||||
|
||||
|
||||
@@ -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.";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 l’historique de messages";
|
||||
"room_participants_leave_success" = "Parti du salon";
|
||||
"room_participants_leave_processing" = "Départ";
|
||||
"notice_error_unformattable_event" = "** Impossible d’afficher le message. Merci de signaler une erreur";
|
||||
"home_syncing" = "Synchronisation";
|
||||
|
||||
@@ -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מנהלי שילובים מקבלים נתוני תצורה, ויכולים להגדיר וו'ידגטים, לשלוח הזמנות חדרים ורמות הספק בשמך.";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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.";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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" = "同期中";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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äť";
|
||||
|
||||
@@ -2124,3 +2124,7 @@
|
||||
"attachment_unsupported_preview_title" = "S’arrihet 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" = "** S’arrihet 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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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" = "Показувати останній аватар та ім'я для користувачів у історії повідомлень";
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
extension Encodable {
|
||||
/// Convenience method to get the json string of an Encodable
|
||||
var jsonString: String? {
|
||||
let encoder = JSONEncoder()
|
||||
guard let jsonData = try? encoder.encode(self) else { return nil }
|
||||
return String(data: jsonData, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixSDK
|
||||
import GBDeviceInfo
|
||||
|
||||
extension MXBugReportRestClient {
|
||||
|
||||
@objc static func vc_bugReportRestClient(appName: String) -> MXBugReportRestClient {
|
||||
let client = MXBugReportRestClient(bugReportEndpoint: BuildSettings.bugReportEndpointUrlString)
|
||||
// App info
|
||||
client.appName = appName
|
||||
client.version = AppDelegate.theDelegate().appVersion
|
||||
client.build = AppDelegate.theDelegate().build
|
||||
|
||||
client.deviceModel = GBDeviceInfo.deviceInfo().modelString
|
||||
client.deviceOS = "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"
|
||||
return client
|
||||
}
|
||||
|
||||
@objc func vc_sendBugReport(
|
||||
description: String,
|
||||
sendLogs: Bool,
|
||||
sendCrashLog: Bool,
|
||||
sendFiles: [URL]? = nil,
|
||||
additionalLabels: [String]? = nil,
|
||||
customFields: [String: String]? = nil,
|
||||
progress: ((MXBugReportState, Progress?) -> Void)? = nil,
|
||||
success: ((String?) -> Void)? = nil,
|
||||
failure: ((Error?) -> Void)? = nil
|
||||
) {
|
||||
// User info (TODO: handle multi-account and find a way to expose them in rageshake API)
|
||||
var userInfo = [String: String]()
|
||||
let mainAccount = MXKAccountManager.shared().accounts.first
|
||||
if let userId = mainAccount?.mxSession.myUser.userId {
|
||||
userInfo["user_id"] = userId
|
||||
}
|
||||
if let deviceId = mainAccount?.mxSession.matrixRestClient.credentials.deviceId {
|
||||
userInfo["device_id"] = deviceId
|
||||
}
|
||||
|
||||
userInfo["locale"] = NSLocale.preferredLanguages[0]
|
||||
userInfo["default_app_language"] = Bundle.main.preferredLocalizations[0] // The language chosen by the OS
|
||||
userInfo["app_language"] = Bundle.mxk_language() ?? userInfo["default_app_language"] // The language chosen by the user
|
||||
|
||||
// Application settings
|
||||
userInfo["lazy_loading"] = MXKAppSettings.standard().syncWithLazyLoadOfRoomMembers ? "ON" : "OFF"
|
||||
|
||||
let currentDate = Date()
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
userInfo["local_time"] = dateFormatter.string(from: currentDate)
|
||||
|
||||
dateFormatter.timeZone = TimeZone(identifier: "UTC")
|
||||
userInfo["utc_time"] = dateFormatter.string(from: currentDate)
|
||||
|
||||
if let customFields = customFields {
|
||||
// combine userInfo with custom fields overriding with custom where there is a conflict
|
||||
userInfo.merge(customFields) { (_, new) in new }
|
||||
}
|
||||
others = userInfo
|
||||
|
||||
var labels: [String] = additionalLabels ?? [String]()
|
||||
// Add a Github label giving information about the version
|
||||
if var versionLabel = version, let buildLabel = build {
|
||||
|
||||
// If this is not the app store version, be more accurate on the build origin
|
||||
if buildLabel == VectorL10n.settingsConfigNoBuildInfo {
|
||||
// This is a debug session from Xcode
|
||||
versionLabel += "-debug"
|
||||
} else if !buildLabel.contains("master") {
|
||||
// This is a Jenkins build. Add the branch and the build number
|
||||
let buildString = buildLabel.replacingOccurrences(of: " ", with: "-")
|
||||
versionLabel += "-\(buildString)"
|
||||
}
|
||||
labels += [versionLabel]
|
||||
}
|
||||
if sendCrashLog {
|
||||
labels += ["crash"]
|
||||
}
|
||||
|
||||
var sendDescription = description
|
||||
if sendCrashLog,
|
||||
let crashLogFile = MXLogger.crashLog(),
|
||||
let crashLog = try? String(contentsOfFile: crashLogFile, encoding: .utf8) {
|
||||
// Append the crash dump to the user description in order to ease triaging of GH issues
|
||||
sendDescription += "\n\n\n--------------------------------------------------------------------------------\n\n\(crashLog)"
|
||||
}
|
||||
|
||||
sendBugReport(sendDescription,
|
||||
sendLogs: sendLogs,
|
||||
sendCrashLog: sendCrashLog,
|
||||
sendFiles: sendFiles,
|
||||
attachGitHubLabels: labels,
|
||||
progress: progress,
|
||||
success: success,
|
||||
failure: failure)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,4 +26,24 @@ extension MXEvent {
|
||||
}
|
||||
return MXMessageType(identifier: messageTypeString)
|
||||
}
|
||||
|
||||
/// Lightweight version of the receiver, in which reply-specific keys are stripped. Returns the same event with the receiver if not a reply event.
|
||||
/// Should be used only to update formatting behavior.
|
||||
var replyStrippedVersion: MXEvent {
|
||||
if self.isReply(), let newMessage = self.copy() as? MXEvent {
|
||||
var jsonDict = newMessage.isEncrypted ? newMessage.clear?.jsonDictionary() : newMessage.jsonDictionary()
|
||||
if var content = jsonDict?["content"] as? [String: Any] {
|
||||
content.removeValue(forKey: "format")
|
||||
content.removeValue(forKey: "formatted_body")
|
||||
content.removeValue(forKey: kMXEventRelationRelatesToKey)
|
||||
if let replyText = MXReplyEventParser().parse(newMessage)?.bodyParts.replyText {
|
||||
content["body"] = replyText
|
||||
}
|
||||
jsonDict?["content"] = content
|
||||
}
|
||||
return MXEvent(fromJSON: jsonDict)
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright 2021 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 Combine
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
extension Publisher {
|
||||
|
||||
///
|
||||
/// Buffer upstream items and guarantee a time interval spacing out the published items.
|
||||
/// - Parameters:
|
||||
/// - spacingDelay: A delay in seconds to guarantee between emissions
|
||||
/// - scheduler: The `DispatchQueue` on which to schedule emissions.
|
||||
/// - Returns: The new wrapped publisher
|
||||
func bufferAndSpace(spacingDelay: Int, scheduler: DispatchQueue = DispatchQueue.main) -> Publishers.FlatMap<
|
||||
Publishers.SetFailureType<Publishers.Delay<Just<Publishers.Buffer<Self>.Output>, DispatchQueue>, Publishers.Buffer<Self>.Failure>,
|
||||
Publishers.Buffer<Self>
|
||||
> {
|
||||
return buffer(size: .max, prefetch: .byRequest, whenFull: .dropNewest)
|
||||
.flatMap(maxPublishers: .max(1)) {
|
||||
Just($0).delay(for: .seconds(spacingDelay), scheduler: scheduler)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,4 +38,38 @@ extension UILabel {
|
||||
self.preferredMaxLayoutWidth = width
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets an HTML string into the receiver. Does not support custom fonts but considers receiver's font size.
|
||||
/// - Parameter htmlText: HTML text to be rendered.
|
||||
@objc func setHTMLFromString(_ htmlText: String) {
|
||||
let htmlTemplate = """
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system;
|
||||
font-size: \(font.pointSize)px;
|
||||
}
|
||||
p:last-child { display: inline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
\(htmlText)
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
guard let data = htmlTemplate.data(using: .utf8),
|
||||
let attributedString = try? NSAttributedString(
|
||||
data: data,
|
||||
options: [.documentType: NSAttributedString.DocumentType.html],
|
||||
documentAttributes: nil
|
||||
) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.attributedText = attributedString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ internal class Asset: NSObject {
|
||||
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 onboardingUseCaseCommunity = ImageAsset(name: "onboarding_use_case_community")
|
||||
internal static let onboardingUseCaseCommunityDark = ImageAsset(name: "onboarding_use_case_community_dark")
|
||||
@@ -172,7 +173,10 @@ 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")
|
||||
internal static let locationShareIcon = ImageAsset(name: "location_share_icon")
|
||||
internal static let locationUserMarker = ImageAsset(name: "location_user_marker")
|
||||
internal static let pollCheckboxDefault = ImageAsset(name: "poll_checkbox_default")
|
||||
|
||||
@@ -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,16 @@ 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"
|
||||
|
||||
internal static let initialScene = InitialSceneType<Riot.ThreadsNoticeViewController>(storyboard: ThreadsNoticeViewController.self)
|
||||
}
|
||||
internal enum UserVerificationSessionStatusViewController: StoryboardType {
|
||||
internal static let storyboardName = "UserVerificationSessionStatusViewController"
|
||||
|
||||
|
||||
@@ -687,6 +687,10 @@ public class VectorL10n: NSObject {
|
||||
public static var callJitsiError: String {
|
||||
return VectorL10n.tr("Vector", "call_jitsi_error")
|
||||
}
|
||||
/// Unable to start conference call
|
||||
public static var callJitsiUnableToStart: String {
|
||||
return VectorL10n.tr("Vector", "call_jitsi_unable_to_start")
|
||||
}
|
||||
/// Device Speaker
|
||||
public static var callMoreActionsAudioUseDevice: String {
|
||||
return VectorL10n.tr("Vector", "call_more_actions_audio_use_device")
|
||||
@@ -931,6 +935,10 @@ public class VectorL10n: NSObject {
|
||||
public static var createRoomPlaceholderTopic: String {
|
||||
return VectorL10n.tr("Vector", "create_room_placeholder_topic")
|
||||
}
|
||||
/// Creating room
|
||||
public static var createRoomProcessing: String {
|
||||
return VectorL10n.tr("Vector", "create_room_processing")
|
||||
}
|
||||
/// PROMOTION
|
||||
public static var createRoomPromotionHeader: String {
|
||||
return VectorL10n.tr("Vector", "create_room_promotion_header")
|
||||
@@ -2215,6 +2223,10 @@ public class VectorL10n: NSObject {
|
||||
public static var joined: String {
|
||||
return VectorL10n.tr("Vector", "joined")
|
||||
}
|
||||
/// Joining
|
||||
public static var joining: String {
|
||||
return VectorL10n.tr("Vector", "joining")
|
||||
}
|
||||
/// Done
|
||||
public static var keyBackupRecoverDoneAction: String {
|
||||
return VectorL10n.tr("Vector", "key_backup_recover_done_action")
|
||||
@@ -2747,6 +2759,10 @@ public class VectorL10n: NSObject {
|
||||
public static var locationSharingInvalidAuthorizationSettings: String {
|
||||
return VectorL10n.tr("Vector", "location_sharing_invalid_authorization_settings")
|
||||
}
|
||||
/// Share live location
|
||||
public static var locationSharingLiveShareTitle: String {
|
||||
return VectorL10n.tr("Vector", "location_sharing_live_share_title")
|
||||
}
|
||||
/// %@ could not load the map. Please try again later.
|
||||
public static func locationSharingLoadingMapErrorTitle(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "location_sharing_loading_map_error_title", p1)
|
||||
@@ -2767,6 +2783,10 @@ public class VectorL10n: NSObject {
|
||||
public static var locationSharingOpenOpenStreetMaps: String {
|
||||
return VectorL10n.tr("Vector", "location_sharing_open_open_street_maps")
|
||||
}
|
||||
/// Send this location
|
||||
public static var locationSharingPinDropShareTitle: String {
|
||||
return VectorL10n.tr("Vector", "location_sharing_pin_drop_share_title")
|
||||
}
|
||||
/// %@ could not send your location. Please try again later.
|
||||
public static func locationSharingPostFailureSubtitle(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "location_sharing_post_failure_subtitle", p1)
|
||||
@@ -2783,9 +2803,9 @@ public class VectorL10n: NSObject {
|
||||
public static var locationSharingSettingsToggleTitle: String {
|
||||
return VectorL10n.tr("Vector", "location_sharing_settings_toggle_title")
|
||||
}
|
||||
/// Share
|
||||
public static var locationSharingShareAction: String {
|
||||
return VectorL10n.tr("Vector", "location_sharing_share_action")
|
||||
/// Send my current location
|
||||
public static var locationSharingStaticShareTitle: String {
|
||||
return VectorL10n.tr("Vector", "location_sharing_static_share_title")
|
||||
}
|
||||
/// Location
|
||||
public static var locationSharingTitle: String {
|
||||
@@ -6687,6 +6707,10 @@ public class VectorL10n: NSObject {
|
||||
public static var settingsLabsE2eEncryptionPromptMessage: String {
|
||||
return VectorL10n.tr("Vector", "settings_labs_e2e_encryption_prompt_message")
|
||||
}
|
||||
/// Auto Report Decryption Errors
|
||||
public static var settingsLabsEnableAutoReportDecryptionErrors: String {
|
||||
return VectorL10n.tr("Vector", "settings_labs_enable_auto_report_decryption_errors")
|
||||
}
|
||||
/// Ring for group calls
|
||||
public static var settingsLabsEnableRingingForGroupCalls: String {
|
||||
return VectorL10n.tr("Vector", "settings_labs_enable_ringing_for_group_calls")
|
||||
@@ -7567,6 +7591,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")
|
||||
@@ -7587,6 +7631,18 @@ public class VectorL10n: NSObject {
|
||||
public static var threadsEmptyTitle: String {
|
||||
return VectorL10n.tr("Vector", "threads_empty_title")
|
||||
}
|
||||
/// Got it
|
||||
public static var threadsNoticeDone: String {
|
||||
return VectorL10n.tr("Vector", "threads_notice_done")
|
||||
}
|
||||
/// 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.
|
||||
public static var threadsNoticeInformation: String {
|
||||
return VectorL10n.tr("Vector", "threads_notice_information")
|
||||
}
|
||||
/// Threads no longer experimental 🎉
|
||||
public static var threadsNoticeTitle: String {
|
||||
return VectorL10n.tr("Vector", "threads_notice_title")
|
||||
}
|
||||
/// Threads
|
||||
public static var threadsTitle: String {
|
||||
return VectorL10n.tr("Vector", "threads_title")
|
||||
|
||||
@@ -26,12 +26,24 @@ public extension VectorL10n {
|
||||
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")
|
||||
}
|
||||
/// You’re 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.
|
||||
public static func onboardingCongratulationsMessage(_ p1: String) -> String {
|
||||
static func onboardingCongratulationsMessage(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Untranslated", "onboarding_congratulations_message", p1)
|
||||
}
|
||||
/// Personalise profile
|
||||
|
||||
@@ -263,24 +263,25 @@ class CallPresenter: NSObject {
|
||||
func processWidgetEvent(_ event: MXEvent, inSession session: MXSession) {
|
||||
MXLog.debug("[CallPresenter] processWidgetEvent")
|
||||
|
||||
guard JMCallKitProxy.isProviderConfigured() else {
|
||||
// CallKit proxy is not configured, no benefit in parsing the event
|
||||
MXLog.debug("[CallPresenter] processWidgetEvent: JMCallKitProxy not configured")
|
||||
return
|
||||
}
|
||||
|
||||
guard let widget = Widget(widgetEvent: event, inMatrixSession: session) else {
|
||||
MXLog.debug("[CallPresenter] processWidgetEvent: widget couldn't be created")
|
||||
return
|
||||
}
|
||||
|
||||
if let uuid = self.jitsiCalls.first(where: { $0.value.widgetId == widget.widgetId })?.key {
|
||||
// this Jitsi call is already managed by this class, no need to report the call again
|
||||
MXLog.debug("[CallPresenter] processWidgetEvent: Jitsi call already managed with id: \(uuid.uuidString)")
|
||||
guard JMCallKitProxy.isProviderConfigured() else {
|
||||
// CallKit proxy is not configured, no benefit in parsing the event
|
||||
MXLog.debug("[CallPresenter] processWidgetEvent: JMCallKitProxy not configured")
|
||||
hangupUnhandledCallIfNeeded(widget)
|
||||
return
|
||||
}
|
||||
|
||||
if widget.isActive {
|
||||
if let uuid = self.jitsiCalls.first(where: { $0.value.widgetId == widget.widgetId })?.key {
|
||||
// this Jitsi call is already managed by this class, no need to report the call again
|
||||
MXLog.debug("[CallPresenter] processWidgetEvent: Jitsi call already managed with id: \(uuid.uuidString)")
|
||||
return
|
||||
}
|
||||
|
||||
guard widget.type == kWidgetTypeJitsiV1 || widget.type == kWidgetTypeJitsiV2 else {
|
||||
// not a Jitsi widget, ignore
|
||||
MXLog.debug("[CallPresenter] processWidgetEvent: not a Jitsi widget")
|
||||
@@ -337,6 +338,7 @@ class CallPresenter: NSObject {
|
||||
guard let uuid = self.jitsiCalls.first(where: { $0.value.widgetId == widget.widgetId })?.key else {
|
||||
// this Jitsi call is not managed by this class
|
||||
MXLog.debug("[CallPresenter] processWidgetEvent: not managed Jitsi call: \(widget.widgetId)")
|
||||
hangupUnhandledCallIfNeeded(widget)
|
||||
return
|
||||
}
|
||||
MXLog.debug("[CallPresenter] processWidgetEvent: ended call with id: \(uuid.uuidString)")
|
||||
@@ -722,6 +724,15 @@ class CallPresenter: NSObject {
|
||||
uiOperationQueue.addOperation(operation)
|
||||
}
|
||||
|
||||
/// Hangs up current Jitsi call, if it is inactive and associated with given widget.
|
||||
/// Should be used for calls that are not handled through JMCallKitProxy,
|
||||
/// as these should be removed regardless.
|
||||
private func hangupUnhandledCallIfNeeded(_ widget: Widget) {
|
||||
guard !widget.isActive, widget.widgetId == jitsiVC?.widget.widgetId else { return }
|
||||
|
||||
MXLog.debug("[CallPresenter] hangupUnhandledCallIfNeeded: ending call with Widget id: %@", widget.widgetId)
|
||||
endActiveJitsiCall()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MXKCallViewControllerDelegate
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
extension RiotSettings {
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
func publisher(for key: String) -> AnyPublisher<Notification, Never> {
|
||||
return NotificationCenter.default.publisher(for: .userDefaultValueUpdated)
|
||||
.filter({ $0.object as? String == key })
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,6 +30,7 @@ final class RiotSettings: NSObject {
|
||||
static let pinRoomsWithMissedNotificationsOnHome = "pinRoomsWithMissedNotif"
|
||||
static let pinRoomsWithUnreadMessagesOnHome = "pinRoomsWithUnread"
|
||||
static let showAllRoomsInHomeSpace = "showAllRoomsInHomeSpace"
|
||||
static let enableUISIAutoReporting = "enableUISIAutoReporting"
|
||||
}
|
||||
|
||||
static let shared = RiotSettings()
|
||||
@@ -146,6 +147,10 @@ final class RiotSettings: NSObject {
|
||||
@UserDefault(key: "enableThreads", defaultValue: false, storage: defaults)
|
||||
var enableThreads
|
||||
|
||||
/// Indicates if auto reporting of decryption errors is enabled
|
||||
@UserDefault(key: UserDefaultsKeys.enableUISIAutoReporting, defaultValue: BuildSettings.cryptoUISIAutoReportingEnabled, storage: defaults)
|
||||
var enableUISIAutoReporting
|
||||
|
||||
// MARK: Calls
|
||||
|
||||
/// Indicate if `allowStunServerFallback` settings has been set once.
|
||||
@@ -347,7 +352,10 @@ final class RiotSettings: NSObject {
|
||||
|
||||
@UserDefault(key: "hideSpaceBetaAnnounce", defaultValue: false, storage: defaults)
|
||||
var hideSpaceBetaAnnounce
|
||||
|
||||
|
||||
@UserDefault(key: "threadsNoticeDisplayed", defaultValue: false, storage: defaults)
|
||||
var threadsNoticeDisplayed
|
||||
|
||||
// MARK: - Version check
|
||||
|
||||
@UserDefault(key: "versionCheckNextDisplayDateTimeInterval", defaultValue: 0.0, storage: defaults)
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixSDK
|
||||
import Combine
|
||||
|
||||
struct UISIAutoReportData {
|
||||
let eventId: String?
|
||||
let roomId: String?
|
||||
let senderKey: String?
|
||||
let deviceId: String?
|
||||
let userId: String?
|
||||
let sessionId: String?
|
||||
}
|
||||
|
||||
extension UISIAutoReportData: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case eventId = "event_id"
|
||||
case roomId = "room_id"
|
||||
case senderKey = "sender_key"
|
||||
case deviceId = "device_id"
|
||||
case userId = "user_id"
|
||||
case sessionId = "session_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Listens for failed decryption events and silently sends reports RageShake server.
|
||||
/// Also requests that message senders send a matching report to have both sides of the interaction.
|
||||
@available(iOS 14.0, *)
|
||||
@objcMembers class UISIAutoReporter: NSObject, UISIDetectorDelegate {
|
||||
|
||||
struct ReportInfo: Hashable {
|
||||
let roomId: String
|
||||
let sessionId: String
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private static let autoRsRequest = "im.vector.auto_rs_request"
|
||||
private static let reportSpacing = 60
|
||||
|
||||
private let bugReporter: MXBugReportRestClient
|
||||
private let dispatchQueue = DispatchQueue(label: "io.element.UISIAutoReporter.queue")
|
||||
// Simple in memory cache of already sent report
|
||||
private var alreadyReportedUisi = Set<ReportInfo>()
|
||||
private let e2eDetectedSubject = PassthroughSubject<UISIDetectedMessage, Never>()
|
||||
private let matchingRSRequestSubject = PassthroughSubject<MXEvent, Never>()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var sessions = [MXSession]()
|
||||
private var enabled = false {
|
||||
didSet {
|
||||
guard oldValue != enabled else { return }
|
||||
detector.enabled = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
override init() {
|
||||
self.bugReporter = MXBugReportRestClient.vc_bugReportRestClient(appName: BuildSettings.bugReportUISIId)
|
||||
super.init()
|
||||
// Simple rate limiting, for any rage-shakes emitted we guarantee a spacing between requests.
|
||||
e2eDetectedSubject
|
||||
.bufferAndSpace(spacingDelay: Self.reportSpacing)
|
||||
.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.sendRageShake(source: $0)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
matchingRSRequestSubject
|
||||
.bufferAndSpace(spacingDelay: Self.reportSpacing)
|
||||
.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.sendMatchingRageShake(source: $0)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
self.enabled = RiotSettings.shared.enableUISIAutoReporting
|
||||
RiotSettings.shared.publisher(for: RiotSettings.UserDefaultsKeys.enableUISIAutoReporting)
|
||||
.sink { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.enabled = RiotSettings.shared.enableUISIAutoReporting
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private lazy var detector: UISIDetector = {
|
||||
let detector = UISIDetector()
|
||||
detector.delegate = self
|
||||
return detector
|
||||
}()
|
||||
|
||||
var reciprocateToDeviceEventType: String {
|
||||
return Self.autoRsRequest
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func uisiDetected(source: UISIDetectedMessage) {
|
||||
dispatchQueue.async {
|
||||
let reportInfo = ReportInfo(roomId: source.roomId, sessionId: source.sessionId)
|
||||
let alreadySent = self.alreadyReportedUisi.contains(reportInfo)
|
||||
if !alreadySent {
|
||||
self.alreadyReportedUisi.insert(reportInfo)
|
||||
self.e2eDetectedSubject.send(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func add(_ session: MXSession) {
|
||||
sessions.append(session)
|
||||
detector.enabled = enabled
|
||||
session.eventStreamService.add(eventStreamListener: detector)
|
||||
}
|
||||
|
||||
func remove(_ session: MXSession) {
|
||||
if let index = sessions.firstIndex(of: session) {
|
||||
sessions.remove(at: index)
|
||||
}
|
||||
session.eventStreamService.remove(eventStreamListener: detector)
|
||||
}
|
||||
|
||||
func uisiReciprocateRequest(source: MXEvent) {
|
||||
guard source.type == Self.autoRsRequest else { return }
|
||||
self.matchingRSRequestSubject.send(source)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func sendRageShake(source: UISIDetectedMessage) {
|
||||
MXLog.debug("[UISIAutoReporter] sendRageShake")
|
||||
guard let session = sessions.first else { return }
|
||||
let uisiData = UISIAutoReportData(
|
||||
eventId: source.eventId,
|
||||
roomId: source.roomId,
|
||||
senderKey: source.senderKey,
|
||||
deviceId: source.senderDeviceId,
|
||||
userId: source.senderUserId,
|
||||
sessionId: source.sessionId
|
||||
).jsonString ?? ""
|
||||
|
||||
self.bugReporter.vc_sendBugReport(
|
||||
description: "Auto-reporting decryption error",
|
||||
sendLogs: true,
|
||||
sendCrashLog: true,
|
||||
additionalLabels: [
|
||||
"Z-UISI",
|
||||
"ios",
|
||||
"uisi-recipient"
|
||||
],
|
||||
customFields: ["auto_uisi": uisiData],
|
||||
success: { reportUrl in
|
||||
let contentMap = MXUsersDevicesMap<NSDictionary>()
|
||||
let content = [
|
||||
"event_id": source.eventId,
|
||||
"room_id": source.roomId,
|
||||
"session_id": source.sessionId,
|
||||
"device_id": source.senderDeviceId,
|
||||
"user_id": source.senderUserId,
|
||||
"sender_key": source.senderKey,
|
||||
"recipient_rageshake": reportUrl
|
||||
]
|
||||
contentMap.setObject(content as NSDictionary, forUser: source.senderUserId, andDevice: source.senderDeviceId)
|
||||
session.matrixRestClient.sendDirectToDevice(
|
||||
eventType: Self.autoRsRequest,
|
||||
contentMap: contentMap,
|
||||
txnId: nil
|
||||
) { response in
|
||||
if response.isFailure {
|
||||
MXLog.warning("failed to send auto-uisi to device")
|
||||
}
|
||||
}
|
||||
},
|
||||
failure: { [weak self] error in
|
||||
guard let self = self else { return }
|
||||
self.dispatchQueue.async {
|
||||
self.alreadyReportedUisi.remove(ReportInfo(roomId: source.roomId, sessionId: source.sessionId))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func sendMatchingRageShake(source: MXEvent) {
|
||||
MXLog.debug("[UISIAutoReporter] sendMatchingRageShake")
|
||||
let eventId = source.content["event_id"] as? String
|
||||
let roomId = source.content["room_id"] as? String
|
||||
let sessionId = source.content["session_id"] as? String
|
||||
let deviceId = source.content["device_id"] as? String
|
||||
let userId = source.content["user_id"] as? String
|
||||
let senderKey = source.content["sender_key"] as? String
|
||||
let matchingIssue = source.content["recipient_rageshake"] as? String
|
||||
|
||||
var description = "Auto-reporting decryption error (sender)"
|
||||
if let matchingIssue = matchingIssue {
|
||||
description += "\nRecipient rageshake: \(matchingIssue)"
|
||||
}
|
||||
|
||||
let uisiData = UISIAutoReportData(
|
||||
eventId: eventId,
|
||||
roomId: roomId,
|
||||
senderKey: senderKey,
|
||||
deviceId: deviceId,
|
||||
userId: userId,
|
||||
sessionId: sessionId
|
||||
).jsonString ?? ""
|
||||
|
||||
self.bugReporter.vc_sendBugReport(
|
||||
description: description,
|
||||
sendLogs: true,
|
||||
sendCrashLog: true,
|
||||
additionalLabels: [
|
||||
"Z-UISI",
|
||||
"ios",
|
||||
"uisi-sender"
|
||||
],
|
||||
customFields: [
|
||||
"auto_uisi": uisiData,
|
||||
"recipient_rageshake": matchingIssue ?? ""
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// Copyright 2021 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 MatrixSDK
|
||||
import Foundation
|
||||
|
||||
protocol UISIDetectorDelegate: AnyObject {
|
||||
var reciprocateToDeviceEventType: String { get }
|
||||
func uisiDetected(source: UISIDetectedMessage)
|
||||
func uisiReciprocateRequest(source: MXEvent)
|
||||
}
|
||||
|
||||
struct UISIDetectedMessage {
|
||||
let eventId: String
|
||||
let roomId: String
|
||||
let senderUserId: String
|
||||
let senderDeviceId: String
|
||||
let senderKey: String
|
||||
let sessionId: String
|
||||
|
||||
static func fromEvent(event: MXEvent) -> UISIDetectedMessage {
|
||||
return UISIDetectedMessage(
|
||||
eventId: event.eventId ?? "",
|
||||
roomId: event.roomId,
|
||||
senderUserId: event.sender,
|
||||
senderDeviceId: event.wireContent["device_id"] as? String ?? "",
|
||||
senderKey: event.wireContent["sender_key"] as? String ?? "",
|
||||
sessionId: event.wireContent["session_id"] as? String ?? ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Detects decryption errors that occur and don't recover within a grace period.
|
||||
/// see `UISIDetectorDelegate` for listening to detections.
|
||||
class UISIDetector: MXLiveEventListener {
|
||||
|
||||
weak var delegate: UISIDetectorDelegate?
|
||||
var enabled = false
|
||||
|
||||
var initialSyncCompleted = false
|
||||
private var trackedUISIs = [String: DispatchSourceTimer]()
|
||||
private let dispatchQueue = DispatchQueue(label: "io.element.UISIDetector.queue")
|
||||
private static let gracePeriodSeconds = 30
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func onSessionStateChanged(state: MXSessionState) {
|
||||
dispatchQueue.async {
|
||||
self.initialSyncCompleted = state == .running
|
||||
}
|
||||
}
|
||||
|
||||
func onLiveEventDecryptionAttempted(event: MXEvent, result: MXEventDecryptionResult) {
|
||||
guard enabled, let eventId = event.eventId, let roomId = event.roomId else { return }
|
||||
dispatchQueue.async {
|
||||
let trackedId = Self.trackedEventId(roomId: eventId, eventId: roomId)
|
||||
|
||||
if let timer = self.trackedUISIs[trackedId],
|
||||
result.clearEvent != nil {
|
||||
// successfully decrypted during grace period, cancel timer.
|
||||
self.trackedUISIs[trackedId] = nil
|
||||
timer.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
guard self.initialSyncCompleted,
|
||||
result.clearEvent == nil
|
||||
else { return }
|
||||
|
||||
// track uisi and report it only if it is not decrypted before grade period ends
|
||||
let timer = DispatchSource.makeTimerSource(queue: self.dispatchQueue)
|
||||
timer.schedule(deadline: .now() + .seconds(Self.gracePeriodSeconds))
|
||||
timer.setEventHandler { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.trackedUISIs[trackedId] = nil
|
||||
MXLog.verbose("[UISIDetector] onLiveEventDecryptionAttempted: Timeout on \(eventId)")
|
||||
self.triggerUISI(source: UISIDetectedMessage.fromEvent(event: event))
|
||||
}
|
||||
self.trackedUISIs[trackedId] = timer
|
||||
timer.activate()
|
||||
}
|
||||
}
|
||||
|
||||
func onLiveToDeviceEvent(event: MXEvent) {
|
||||
guard enabled, event.type == delegate?.reciprocateToDeviceEventType else { return }
|
||||
delegate?.uisiReciprocateRequest(source: event)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func triggerUISI(source: UISIDetectedMessage) {
|
||||
guard enabled else { return }
|
||||
MXLog.info("[UISIDetector] triggerUISI: Unable To Decrypt \(source)")
|
||||
self.delegate?.uisiDetected(source: source)
|
||||
}
|
||||
|
||||
// MARK: - Static
|
||||
|
||||
private static func trackedEventId(roomId: String, eventId: String) -> String {
|
||||
return "\(roomId)-\(eventId)"
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,8 @@ typedef enum : NSUInteger
|
||||
WidgetManagerErrorCodeNoIntegrationsServerConfigured,
|
||||
WidgetManagerErrorCodeDisabledIntegrationsServer,
|
||||
WidgetManagerErrorCodeFailedToConnectToIntegrationsServer,
|
||||
WidgetManagerErrorCodeTermsNotSigned
|
||||
WidgetManagerErrorCodeTermsNotSigned,
|
||||
WidgetManagerErrorCodeUnavailableJitsiURL
|
||||
}
|
||||
WidgetManagerErrorCode;
|
||||
|
||||
|
||||
@@ -278,6 +278,13 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
|
||||
NSString *widgetId = [NSString stringWithFormat:@"%@_%@_%@", kWidgetTypeJitsiV1, room.mxSession.myUser.userId, @((uint64_t)([[NSDate date] timeIntervalSince1970] * 1000))];
|
||||
|
||||
NSURL *preferredJitsiServerUrl = [room.mxSession vc_homeserverConfiguration].jitsi.serverURL;
|
||||
|
||||
if (!preferredJitsiServerUrl)
|
||||
{
|
||||
MXLogDebug(@"[WidgetManager] createJitsiWidgetInRoom: Error: No Jitsi server URL provided");
|
||||
failure(self.errorForUnavailableJitsiURL);
|
||||
return nil;
|
||||
}
|
||||
|
||||
JitsiService *jitsiService = JitsiService.shared;
|
||||
|
||||
@@ -807,4 +814,11 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
|
||||
userInfo:@{NSLocalizedDescriptionKey: [VectorL10n widgetIntegrationManagerDisabled]}];
|
||||
}
|
||||
|
||||
- (NSError*)errorForUnavailableJitsiURL
|
||||
{
|
||||
return [NSError errorWithDomain:WidgetManagerErrorDomain
|
||||
code:WidgetManagerErrorCodeUnavailableJitsiURL
|
||||
userInfo:@{NSLocalizedDescriptionKey: VectorL10n.callJitsiUnableToStart}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -54,18 +54,15 @@ final class HomeserverConfigurationBuilder: NSObject {
|
||||
secureBackupSetupMethods: secureBackupSetupMethods)
|
||||
|
||||
// Jitsi configuration
|
||||
let jitsiPreferredDomain: String
|
||||
let jitsiServerURL: URL
|
||||
let hardcodedJitsiServerURL: URL = BuildSettings.jitsiServerUrl
|
||||
let jitsiPreferredDomain: String?
|
||||
let jitsiServerURL: URL?
|
||||
let hardcodedJitsiServerURL: URL? = BuildSettings.jitsiServerUrl
|
||||
|
||||
if let preferredDomain = vectorWellKnownJitsiConfiguration?.preferredDomain {
|
||||
jitsiPreferredDomain = preferredDomain
|
||||
jitsiServerURL = self.jitsiServerURL(from: preferredDomain) ?? hardcodedJitsiServerURL
|
||||
} else {
|
||||
guard let hardcodedJitsiDomain = hardcodedJitsiServerURL.host else {
|
||||
fatalError("[HomeserverConfigurationBuilder] Fail to get Jitsi domain from hardcoded Jitsi URL")
|
||||
}
|
||||
jitsiPreferredDomain = hardcodedJitsiDomain
|
||||
jitsiPreferredDomain = hardcodedJitsiServerURL?.host
|
||||
jitsiServerURL = hardcodedJitsiServerURL
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ import Foundation
|
||||
/// `HomeserverJitsiConfiguration` gives Jitsi widget configuration used by homeserver
|
||||
@objcMembers
|
||||
final class HomeserverJitsiConfiguration: NSObject {
|
||||
let serverDomain: String
|
||||
let serverURL: URL
|
||||
let serverDomain: String?
|
||||
let serverURL: URL?
|
||||
|
||||
init(serverDomain: String, serverURL: URL) {
|
||||
init(serverDomain: String?, serverURL: URL?) {
|
||||
self.serverDomain = serverDomain
|
||||
self.serverURL = serverURL
|
||||
|
||||
|
||||
@@ -227,14 +227,6 @@ UINavigationControllerDelegate
|
||||
// Reopen an existing direct room with this userId or creates a new one (if it doesn't exist)
|
||||
- (void)startDirectChatWithUserId:(NSString*)userId completion:(void (^)(void))completion;
|
||||
|
||||
/**
|
||||
Process the fragment part of a vector.im link.
|
||||
|
||||
@param fragment the fragment part of the universal link.
|
||||
@return YES in case of processing success.
|
||||
*/
|
||||
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment;
|
||||
|
||||
/**
|
||||
Process the fragment part of a vector.im link.
|
||||
|
||||
|
||||
@@ -225,6 +225,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
@property (nonatomic, strong) PushNotificationStore *pushNotificationStore;
|
||||
@property (nonatomic, strong) LocalAuthenticationService *localAuthenticationService;
|
||||
@property (nonatomic, strong, readwrite) CallPresenter *callPresenter;
|
||||
@property (nonatomic, strong, readwrite) id uisiAutoReporter;
|
||||
|
||||
@property (nonatomic, strong) MajorUpdateManager *majorUpdateManager;
|
||||
|
||||
@@ -393,6 +394,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
[NSBundle mxk_setLanguage:language];
|
||||
[NSBundle mxk_setFallbackLanguage:@"en"];
|
||||
|
||||
if (BuildSettings.disableRightToLeftLayout)
|
||||
{
|
||||
[[UIView appearance] setSemanticContentAttribute:UISemanticContentAttributeForceLeftToRight];
|
||||
}
|
||||
|
||||
// Set app info now as Mac (Designed for iPad) accesses it before didFinishLaunching is called
|
||||
self.appInfo = AppInfo.current;
|
||||
|
||||
@@ -471,14 +477,23 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
|
||||
self.spaceFeatureUnavailablePresenter = [SpaceFeatureUnavailablePresenter new];
|
||||
|
||||
if (@available(iOS 14.0, *)) {
|
||||
self.uisiAutoReporter = [[UISIAutoReporter alloc] init];
|
||||
}
|
||||
|
||||
// Add matrix observers, and initialize matrix sessions if the app is not launched in background.
|
||||
[self initMatrixSessions];
|
||||
|
||||
#ifdef CALL_STACK_JINGLE
|
||||
// Setup Jitsi
|
||||
[JitsiService.shared configureDefaultConferenceOptionsWith:BuildSettings.jitsiServerUrl];
|
||||
NSURL *jitsiServerUrl = BuildSettings.jitsiServerUrl;
|
||||
if (jitsiServerUrl)
|
||||
{
|
||||
[JitsiService.shared configureDefaultConferenceOptionsWith:jitsiServerUrl];
|
||||
|
||||
[JitsiService.shared application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
|
||||
[JitsiService.shared application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
#endif
|
||||
|
||||
self.majorUpdateManager = [MajorUpdateManager new];
|
||||
@@ -932,6 +947,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
|
||||
NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
|
||||
NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
|
||||
NSString *localizedDescription = error.localizedDescription;
|
||||
if (!title)
|
||||
{
|
||||
if (msg)
|
||||
@@ -939,6 +955,10 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
title = msg;
|
||||
msg = nil;
|
||||
}
|
||||
else if (localizedDescription.length > 0)
|
||||
{
|
||||
title = localizedDescription;
|
||||
}
|
||||
else
|
||||
{
|
||||
title = [VectorL10n error];
|
||||
@@ -1264,15 +1284,14 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
return [self handleUniversalLinkFragment:webURL.fragment fromURL:webURL];
|
||||
}
|
||||
|
||||
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment
|
||||
{
|
||||
return [self handleUniversalLinkFragment:fragment fromURL:nil];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromURL:(NSURL*)universalLinkURL
|
||||
|
||||
{
|
||||
if (!fragment || !universalLinkURL)
|
||||
{
|
||||
MXLogDebug(@"[AppDelegate] Cannot handle universal link with missing data: %@ %@", fragment, universalLinkURL);
|
||||
return NO;
|
||||
}
|
||||
ScreenPresentationParameters *presentationParameters = [[ScreenPresentationParameters alloc] initWithRestoreInitialDisplay:YES stackAboveVisibleViews:NO];
|
||||
|
||||
UniversalLinkParameters *parameters = [[UniversalLinkParameters alloc] initWithFragment:fragment universalLinkURL:universalLinkURL presentationParameters:presentationParameters];
|
||||
@@ -1473,7 +1492,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
// Ask the HS to resolve the room alias into a room id and then retry
|
||||
self->universalLinkFragmentPending = fragment;
|
||||
MXKAccount* account = accountManager.activeAccounts.firstObject;
|
||||
[account.mxSession.matrixRestClient roomIDForRoomAlias:roomIdOrAlias success:^(NSString *roomId) {
|
||||
[account.mxSession.matrixRestClient resolveRoomAlias:roomIdOrAlias success:^(MXRoomAliasResolution *resolution) {
|
||||
|
||||
// Note: the activity indicator will not disappear if the session is not ready
|
||||
[homeViewController stopActivityIndicator];
|
||||
@@ -1481,34 +1500,20 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
// Check that 'fragment' has not been cancelled
|
||||
if ([self->universalLinkFragmentPending isEqualToString:fragment])
|
||||
{
|
||||
// Retry opening the link but with the returned room id
|
||||
NSString *newUniversalLinkFragment =
|
||||
[fragment stringByReplacingOccurrencesOfString:[MXTools encodeURIComponent:roomIdOrAlias]
|
||||
withString:[MXTools encodeURIComponent:roomId]
|
||||
];
|
||||
|
||||
// The previous operation can fail because of percent encoding
|
||||
// TBH we are not clean on data inputs. For the moment, just give another try with no encoding
|
||||
// TODO: Have a dedicated module and tests to handle universal links (matrix.to, email link, etc)
|
||||
if ([newUniversalLinkFragment isEqualToString:fragment])
|
||||
NSString *newFragment = resolution.deeplinkFragment;
|
||||
if (newFragment && ![newFragment isEqualToString:fragment])
|
||||
{
|
||||
newUniversalLinkFragment =
|
||||
[fragment stringByReplacingOccurrencesOfString:roomIdOrAlias
|
||||
withString:[MXTools encodeURIComponent:roomId]];
|
||||
}
|
||||
|
||||
if (![newUniversalLinkFragment isEqualToString:fragment])
|
||||
{
|
||||
self->universalLinkFragmentPendingRoomAlias = @{roomId: roomIdOrAlias};
|
||||
|
||||
UniversalLinkParameters *newParameters = [[UniversalLinkParameters alloc] initWithFragment:newUniversalLinkFragment universalLinkURL:universalLinkURL presentationParameters:presentationParameters];
|
||||
|
||||
self->universalLinkFragmentPendingRoomAlias = @{resolution.roomId: roomIdOrAlias};
|
||||
|
||||
UniversalLinkParameters *newParameters = [[UniversalLinkParameters alloc] initWithFragment:newFragment
|
||||
universalLinkURL:universalLinkURL
|
||||
presentationParameters:presentationParameters];
|
||||
[self handleUniversalLinkWithParameters:newParameters];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do not continue. Else we will loop forever
|
||||
MXLogDebug(@"[AppDelegate] Universal link: Error: Cannot resolve alias in %@ to the room id %@", fragment, roomId);
|
||||
MXLogDebug(@"[AppDelegate] Universal link: Error: Cannot resolve alias in %@ to the room id %@", fragment, resolution.roomId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2149,6 +2154,16 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
// register the session to the call service
|
||||
[_callPresenter addMatrixSession:mxSession];
|
||||
|
||||
// register the session to the uisi auto-reporter
|
||||
if (_uisiAutoReporter != nil)
|
||||
{
|
||||
if (@available(iOS 14.0, *))
|
||||
{
|
||||
UISIAutoReporter* uisiAutoReporter = (UISIAutoReporter*)_uisiAutoReporter;
|
||||
[uisiAutoReporter add:mxSession];
|
||||
}
|
||||
}
|
||||
|
||||
[mxSessionArray addObject:mxSession];
|
||||
|
||||
// Do the one time check on device id
|
||||
@@ -2164,6 +2179,16 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
|
||||
// remove session from the call service
|
||||
[_callPresenter removeMatrixSession:mxSession];
|
||||
|
||||
// register the session to the uisi auto-reporter
|
||||
if (_uisiAutoReporter != nil)
|
||||
{
|
||||
if (@available(iOS 14.0, *))
|
||||
{
|
||||
UISIAutoReporter* uisiAutoReporter = (UISIAutoReporter*)_uisiAutoReporter;
|
||||
[uisiAutoReporter remove:mxSession];
|
||||
}
|
||||
}
|
||||
|
||||
// Update the widgets manager
|
||||
[[WidgetManager sharedManager] removeMatrixSession:mxSession];
|
||||
@@ -2392,6 +2417,18 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
|
||||
if (mainSession)
|
||||
{
|
||||
|
||||
switch (mainSession.state)
|
||||
{
|
||||
case MXSessionStateClosed:
|
||||
case MXSessionStateInitialised:
|
||||
case MXSessionStateBackgroundSyncInProgress:
|
||||
self.roomListDataReady = NO;
|
||||
[self listenForRoomListDataReady];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
BOOL isLaunching = NO;
|
||||
|
||||
if (_masterTabBarController.isOnboardingInProgress)
|
||||
@@ -2409,8 +2446,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
||||
case MXSessionStateClosed:
|
||||
case MXSessionStateInitialised:
|
||||
case MXSessionStateBackgroundSyncInProgress:
|
||||
self.roomListDataReady = NO;
|
||||
[self listenForRoomListDataReady];
|
||||
isLaunching = YES;
|
||||
break;
|
||||
case MXSessionStateStoreDataReady:
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" keyboardDismissMode="interactive" translatesAutoresizingMaskIntoConstraints="NO" id="OHV-KQ-Ww0">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="768"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rhx-dD-4EJ" userLabel="Content View">
|
||||
<view contentMode="scaleToFill" layoutMarginsFollowReadableWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rhx-dD-4EJ" userLabel="Content View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="485"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="horizontal_logo" translatesAutoresizingMaskIntoConstraints="NO" id="d8r-TX-pwX" userLabel="Welcome Image View">
|
||||
@@ -417,21 +417,21 @@ Clear it if you're finished using this device, or want to sign in to another acc
|
||||
<accessibility key="accessibilityConfiguration" identifier="AuthContentView"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="485" id="6v6-fz-e8o"/>
|
||||
<constraint firstItem="Gg0-TE-OGb" firstAttribute="width" secondItem="rhx-dD-4EJ" secondAttribute="width" id="EBX-KN-pRT"/>
|
||||
<constraint firstItem="TjK-XL-dQS" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leading" id="MId-fr-A7b"/>
|
||||
<constraint firstItem="TjK-XL-dQS" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leadingMargin" id="MId-fr-A7b"/>
|
||||
<constraint firstAttribute="bottom" secondItem="TjK-XL-dQS" secondAttribute="bottom" id="PKr-aT-3Qe"/>
|
||||
<constraint firstItem="TjK-XL-dQS" firstAttribute="top" secondItem="Gg0-TE-OGb" secondAttribute="bottom" id="Sfy-Zn-JkS"/>
|
||||
<constraint firstItem="Gg0-TE-OGb" firstAttribute="top" secondItem="rhx-dD-4EJ" secondAttribute="top" priority="250" constant="384" id="UEM-Mh-0H9"/>
|
||||
<constraint firstItem="xWb-IJ-v7F" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leading" id="YnP-Nk-QxR"/>
|
||||
<constraint firstItem="xWb-IJ-v7F" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leadingMargin" id="YnP-Nk-QxR"/>
|
||||
<constraint firstItem="Gg0-TE-OGb" firstAttribute="top" secondItem="xWb-IJ-v7F" secondAttribute="bottom" id="aGH-m3-xL3"/>
|
||||
<constraint firstAttribute="trailing" secondItem="TjK-XL-dQS" secondAttribute="trailing" id="d1j-OA-Wwm"/>
|
||||
<constraint firstAttribute="trailing" secondItem="xWb-IJ-v7F" secondAttribute="trailing" id="hko-ol-XDd"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="TjK-XL-dQS" secondAttribute="trailing" id="d1j-OA-Wwm"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="xWb-IJ-v7F" secondAttribute="trailing" id="hko-ol-XDd"/>
|
||||
<constraint firstItem="xWb-IJ-v7F" firstAttribute="top" secondItem="rhx-dD-4EJ" secondAttribute="top" constant="160" id="khR-Uj-OTH"/>
|
||||
<constraint firstItem="d8r-TX-pwX" firstAttribute="top" secondItem="rhx-dD-4EJ" secondAttribute="top" constant="35" id="l68-Ta-YKg"/>
|
||||
<constraint firstAttribute="centerX" secondItem="d8r-TX-pwX" secondAttribute="centerX" id="l6k-EH-Yb8"/>
|
||||
<constraint firstItem="Gg0-TE-OGb" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leading" id="rS3-go-zbf"/>
|
||||
<constraint firstItem="TjK-XL-dQS" firstAttribute="width" secondItem="rhx-dD-4EJ" secondAttribute="width" id="zIZ-fl-i3i"/>
|
||||
<constraint firstItem="Gg0-TE-OGb" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leadingMargin" id="rS3-go-zbf"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Gg0-TE-OGb" secondAttribute="trailing" id="umc-1H-5ho"/>
|
||||
</constraints>
|
||||
<edgeInsets key="layoutMargins" top="0.0" left="0.0" bottom="0.0" right="0.0"/>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
|
||||
@@ -295,47 +295,8 @@
|
||||
{
|
||||
self.isSendingLogs = YES;
|
||||
|
||||
// Setup data to send
|
||||
bugReportRestClient = [[MXBugReportRestClient alloc] initWithBugReportEndpoint:BuildSettings.bugReportEndpointUrlString];
|
||||
|
||||
// App info
|
||||
bugReportRestClient.appName = BuildSettings.bugReportApplicationId;
|
||||
bugReportRestClient.version = [AppDelegate theDelegate].appVersion;
|
||||
bugReportRestClient.build = [AppDelegate theDelegate].build;
|
||||
|
||||
// Device info
|
||||
bugReportRestClient.deviceModel = [GBDeviceInfo deviceInfo].modelString;
|
||||
bugReportRestClient.deviceOS = [NSString stringWithFormat:@"%@ %@", [[UIDevice currentDevice] systemName], [[UIDevice currentDevice] systemVersion]];
|
||||
|
||||
// User info (TODO: handle multi-account and find a way to expose them in rageshake API)
|
||||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
||||
MXKAccount *mainAccount = [MXKAccountManager sharedManager].accounts.firstObject;
|
||||
if (mainAccount.mxSession.myUser.userId)
|
||||
{
|
||||
userInfo[@"user_id"] = mainAccount.mxSession.myUser.userId;
|
||||
}
|
||||
if (mainAccount.mxSession.matrixRestClient.credentials.deviceId)
|
||||
{
|
||||
userInfo[@"device_id"] = mainAccount.mxSession.matrixRestClient.credentials.deviceId;
|
||||
}
|
||||
|
||||
userInfo[@"locale"] = [NSLocale preferredLanguages][0];
|
||||
userInfo[@"default_app_language"] = [[NSBundle mainBundle] preferredLocalizations][0]; // The language chosen by the OS
|
||||
userInfo[@"app_language"] = [NSBundle mxk_language] ? [NSBundle mxk_language] : userInfo[@"default_app_language"]; // The language chosen by the user
|
||||
|
||||
// Application settings
|
||||
userInfo[@"lazy_loading"] = [MXKAppSettings standardAppSettings].syncWithLazyLoadOfRoomMembers ? @"ON" : @"OFF";
|
||||
|
||||
NSDate *currentDate = [NSDate date];
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
|
||||
userInfo[@"local_time"] = [dateFormatter stringFromDate:currentDate];
|
||||
|
||||
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
|
||||
userInfo[@"utc_time"] = [dateFormatter stringFromDate:currentDate];
|
||||
|
||||
bugReportRestClient.others = userInfo;
|
||||
|
||||
bugReportRestClient = [MXBugReportRestClient vc_bugReportRestClientWithAppName:BuildSettings.bugReportApplicationId];
|
||||
|
||||
// Screenshot
|
||||
NSArray<NSURL*> *files;
|
||||
if (_screenshot && _sendScreenshot)
|
||||
@@ -347,56 +308,23 @@
|
||||
|
||||
files = @[screenShotFile];
|
||||
}
|
||||
|
||||
// Prepare labels to attach to the GitHub issue
|
||||
NSMutableArray<NSString*> *gitHubLabels = [NSMutableArray array];
|
||||
if (_reportCrash)
|
||||
{
|
||||
// Label the GH issue as "crash"
|
||||
[gitHubLabels addObject:@"crash"];
|
||||
}
|
||||
|
||||
// Add a Github label giving information about the version
|
||||
if (bugReportRestClient.version && bugReportRestClient.build)
|
||||
{
|
||||
NSString *build = bugReportRestClient.build;
|
||||
NSString *versionLabel = bugReportRestClient.version;
|
||||
|
||||
// If this is not the app store version, be more accurate on the build origin
|
||||
if ([build isEqualToString:[VectorL10n settingsConfigNoBuildInfo]])
|
||||
{
|
||||
// This is a debug session from Xcode
|
||||
versionLabel = [versionLabel stringByAppendingString:@"-debug"];
|
||||
}
|
||||
else if (build && ![build containsString:@"master"])
|
||||
{
|
||||
// This is a Jenkins build. Add the branch and the build number
|
||||
NSString *buildString = [build stringByReplacingOccurrencesOfString:@" " withString:@"-"];
|
||||
versionLabel = [[versionLabel stringByAppendingString:@"-"] stringByAppendingString:buildString];
|
||||
}
|
||||
|
||||
[gitHubLabels addObject:versionLabel];
|
||||
}
|
||||
|
||||
|
||||
NSMutableString *bugReportDescription = [NSMutableString stringWithString:_bugReportDescriptionTextView.text];
|
||||
|
||||
if (_reportCrash)
|
||||
{
|
||||
// Append the crash dump to the user description in order to ease triaging of GH issues
|
||||
NSString *crashLogFile = [MXLogger crashLog];
|
||||
NSString *crashLog = [NSString stringWithContentsOfFile:crashLogFile encoding:NSUTF8StringEncoding error:nil];
|
||||
[bugReportDescription appendFormat:@"\n\n\n--------------------------------------------------------------------------------\n\n%@", crashLog];
|
||||
}
|
||||
|
||||
|
||||
// starting a background task to have a bit of extra time in case of user forgets about the report and sends the app to background
|
||||
__block UIBackgroundTaskIdentifier operationBackgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
|
||||
[[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId];
|
||||
operationBackgroundId = UIBackgroundTaskInvalid;
|
||||
}];
|
||||
|
||||
// Submit
|
||||
[bugReportRestClient sendBugReport:bugReportDescription sendLogs:_sendLogs sendCrashLog:_reportCrash sendFiles:files attachGitHubLabels:gitHubLabels progress:^(MXBugReportState state, NSProgress *progress) {
|
||||
|
||||
[bugReportRestClient vc_sendBugReportWithDescription:bugReportDescription
|
||||
sendLogs:_sendLogs
|
||||
sendCrashLog:_reportCrash
|
||||
sendFiles:files
|
||||
additionalLabels:nil
|
||||
customFields:nil
|
||||
progress:^(MXBugReportState state, NSProgress *progress) {
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case MXBugReportStateProgressZipping:
|
||||
@@ -413,7 +341,7 @@
|
||||
|
||||
self.sendingProgress.progress = progress.fractionCompleted;
|
||||
|
||||
} success:^{
|
||||
} success:^(NSString *reportUrl){
|
||||
|
||||
self->bugReportRestClient = nil;
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CommonKit
|
||||
|
||||
/// A convenience objc-compatible wrapper around `UserIndicatorTypePresenterProtocol`.
|
||||
///
|
||||
/// This class wraps swift-only protocol by exposing multiple methods instead of accepting struct types
|
||||
/// and it keeps a track of `UserIndicator`s instead of returning them to the caller.
|
||||
@objc final class UserIndicatorPresenterWrapper: NSObject {
|
||||
private let presenter: UserIndicatorTypePresenterProtocol
|
||||
private var loadingIndicator: UserIndicator?
|
||||
private var otherIndicators = [UserIndicator]()
|
||||
|
||||
init(presenter: UserIndicatorTypePresenterProtocol) {
|
||||
self.presenter = presenter
|
||||
}
|
||||
|
||||
@objc func presentLoadingIndicator() {
|
||||
presentLoadingIndicator(label: VectorL10n.homeSyncing)
|
||||
}
|
||||
|
||||
@objc func presentLoadingIndicator(label: String) {
|
||||
guard loadingIndicator == nil else {
|
||||
// The app is very liberal with calling `presentLoadingIndicator` (often not matched by corresponding `dismissLoadingIndicator`),
|
||||
// so there is no reason to keep adding new indiciators if there is one already showing.
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.debug("[UserIndicatorPresenterWrapper] Present loading indicator")
|
||||
loadingIndicator = presenter.present(.loading(label: label, isInteractionBlocking: false))
|
||||
}
|
||||
|
||||
@objc func dismissLoadingIndicator() {
|
||||
MXLog.debug("[UserIndicatorPresenterWrapper] Dismiss loading indicator")
|
||||
loadingIndicator = nil
|
||||
}
|
||||
|
||||
@objc func presentSuccess(label: String) {
|
||||
presenter.present(.success(label: label)).store(in: &otherIndicators)
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,8 @@
|
||||
|
||||
@class RootTabEmptyView;
|
||||
@class AnalyticsScreenTracker;
|
||||
@class UserIndicatorPresenterWrapper;
|
||||
@class UserIndicatorStore;
|
||||
@class RecentCellContextMenuProvider;
|
||||
|
||||
/**
|
||||
Notification to be posted when recents data is ready. Notification object will be the RecentsViewController instance.
|
||||
@@ -98,9 +99,12 @@ FOUNDATION_EXPORT NSString *const RecentsViewControllerDataReadyNotification;
|
||||
@property (nonatomic) AnalyticsScreenTracker *screenTracker;
|
||||
|
||||
/**
|
||||
Presenter for displaying app-wide user indicators. If not set, the view controller will use legacy activity indicators
|
||||
A store of user indicators that lets the room present and dismiss indicators without
|
||||
worrying about the presentation context or memory management.
|
||||
*/
|
||||
@property (nonatomic, strong) UserIndicatorPresenterWrapper *indicatorPresenter;
|
||||
@property (nonatomic, strong) UserIndicatorStore *userIndicatorStore;
|
||||
|
||||
@property (nonatomic, readonly) RecentCellContextMenuProvider *contextMenuProvider;
|
||||
|
||||
/**
|
||||
Return the sticky header for the specified section of the table view
|
||||
@@ -197,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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
@@ -69,6 +69,9 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
|
||||
|
||||
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
|
||||
__weak id kThemeServiceDidChangeThemeNotificationObserver;
|
||||
|
||||
// Cancel handler of any ongoing loading indicator
|
||||
UserIndicatorCancel loadingIndicatorCancel;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) CreateRoomCoordinatorBridgePresenter *createRoomCoordinatorBridgePresenter;
|
||||
@@ -135,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;
|
||||
}
|
||||
@@ -1305,7 +1311,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
[self stopActivityIndicator];
|
||||
[self.indicatorPresenter presentSuccessWithLabel:[VectorL10n roomParticipantsLeaveSuccess]];
|
||||
[self.userIndicatorStore presentSuccessWithLabel:[VectorL10n roomParticipantsLeaveSuccess]];
|
||||
// Force table refresh
|
||||
[self cancelEditionMode:YES];
|
||||
}
|
||||
@@ -1566,7 +1572,8 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
|
||||
{
|
||||
// Open the room or preview it
|
||||
NSString *fragment = [NSString stringWithFormat:@"/room/%@", [MXTools encodeURIComponent:roomIdOrAlias]];
|
||||
[[AppDelegate theDelegate] handleUniversalLinkFragment:fragment];
|
||||
NSURL *url = [NSURL URLWithString:[MXTools permalinkToRoom:fragment]];
|
||||
[[AppDelegate theDelegate] handleUniversalLinkFragment:fragment fromURL:url];
|
||||
}
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:NO];
|
||||
}
|
||||
@@ -2449,31 +2456,103 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
|
||||
#pragma mark - Activity Indicator
|
||||
|
||||
- (BOOL)providesCustomActivityIndicator {
|
||||
return self.indicatorPresenter != nil;
|
||||
return self.userIndicatorStore != nil;
|
||||
}
|
||||
|
||||
- (void)startActivityIndicatorWithLabel:(NSString *)label {
|
||||
if (self.indicatorPresenter && isViewVisible) {
|
||||
[self.indicatorPresenter presentLoadingIndicatorWithLabel:label];
|
||||
if (self.userIndicatorStore && isViewVisible) {
|
||||
// The app is very liberal with calling `startActivityIndicator` (often not matched by corresponding `stopActivityIndicator`),
|
||||
// so there is no reason to keep adding new indicators if there is one already showing.
|
||||
if (loadingIndicatorCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
MXLogDebug(@"[RecentsViewController] Present loading indicator")
|
||||
loadingIndicatorCancel = [self.userIndicatorStore presentLoadingWithLabel:label isInteractionBlocking:NO];
|
||||
} else {
|
||||
[super startActivityIndicator];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startActivityIndicator {
|
||||
if (self.indicatorPresenter && isViewVisible) {
|
||||
[self.indicatorPresenter presentLoadingIndicator];
|
||||
} else {
|
||||
[super startActivityIndicator];
|
||||
}
|
||||
[self startActivityIndicatorWithLabel:[VectorL10n homeSyncing]];
|
||||
}
|
||||
|
||||
- (void)stopActivityIndicator {
|
||||
if (self.indicatorPresenter) {
|
||||
[self.indicatorPresenter dismissLoadingIndicator];
|
||||
if (self.userIndicatorStore) {
|
||||
if (loadingIndicatorCancel) {
|
||||
MXLogDebug(@"[RecentsViewController] Present loading indicator")
|
||||
loadingIndicatorCancel();
|
||||
loadingIndicatorCancel = nil;
|
||||
}
|
||||
} else {
|
||||
[super stopActivityIndicator];
|
||||
}
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
[animator addCompletion:^{
|
||||
[self showRoomWithRoomId:roomId inMatrixSession:self.mainSession];
|
||||
}];
|
||||
}
|
||||
|
||||
#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
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
#ifndef UserIndicatorCancel_h
|
||||
#define UserIndicatorCancel_h
|
||||
|
||||
/**
|
||||
Callback function to cancel a `UserIndicator` without needing a direct reference to the object
|
||||
|
||||
Note: the function is defined in Objective-C (instead of Swift) to be accessible by both languages.
|
||||
*/
|
||||
typedef void (^UserIndicatorCancel)(void);
|
||||
|
||||
#endif /* UserIndicatorCancel_h */
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CommonKit
|
||||
|
||||
/// An abstraction on top of `UserIndicatorTypePresenterProtocol` which manages and stores the individual user indicators.
|
||||
/// When used to present an indicator the `UserIndicatorStore` will instead returns a simple callback function to the clients
|
||||
/// letting them cancel the indicators without worrying about memory.
|
||||
@objc final class UserIndicatorStore: NSObject {
|
||||
private let presenter: UserIndicatorTypePresenterProtocol
|
||||
private var indicators: [UserIndicator]
|
||||
|
||||
init(presenter: UserIndicatorTypePresenterProtocol) {
|
||||
self.presenter = presenter
|
||||
self.indicators = []
|
||||
}
|
||||
|
||||
/// Present a new type of user indicator, such as loading spinner or success message.
|
||||
/// To remove an indicator, call the returned `UserIndicatorCancel` function
|
||||
func present(type: UserIndicatorType) -> UserIndicatorCancel {
|
||||
let indicator = presenter.present(type)
|
||||
indicators.append(indicator)
|
||||
return {
|
||||
indicator.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
/// Present a loading indicator.
|
||||
/// To remove the indicator call the returned `UserIndicatorCancel` function
|
||||
///
|
||||
/// Note: This is a convenience function callable by objective-c code
|
||||
@objc func presentLoading(label: String, isInteractionBlocking: Bool) -> UserIndicatorCancel {
|
||||
present(
|
||||
type: .loading(
|
||||
label: label,
|
||||
isInteractionBlocking: isInteractionBlocking
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// Present a success message that will be automatically dismissed after a few seconds.
|
||||
///
|
||||
/// Note: This is a convenience function callable by objective-c code
|
||||
@objc func presentSuccess(label: String) {
|
||||
let indicator = presenter.present(.success(label: label))
|
||||
indicators.append(indicator)
|
||||
}
|
||||
}
|
||||
@@ -867,14 +867,13 @@
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self startActivityIndicator];
|
||||
|
||||
[self.mxSession.matrixRestClient roomIDForRoomAlias:roomIdOrAlias success:^(NSString *roomId) {
|
||||
|
||||
[self.mxSession.matrixRestClient resolveRoomAlias:roomIdOrAlias success:^(MXRoomAliasResolution *resolution) {
|
||||
if (roomId && weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
|
||||
[self stopActivityIndicator];
|
||||
[self didSelectRoomId:roomId];
|
||||
[self didSelectRoomId:resolution.roomId];
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
@@ -890,7 +889,7 @@
|
||||
// Open the group or preview it
|
||||
NSString *fragment = [NSString stringWithFormat:@"/group/%@",
|
||||
[MXTools encodeURIComponent:absoluteURLString]];
|
||||
[[AppDelegate theDelegate] handleUniversalLinkFragment:fragment];
|
||||
[[AppDelegate theDelegate] handleUniversalLinkFragment:fragment fromURL:URL];
|
||||
}
|
||||
|
||||
return shouldInteractWithURL;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -60,11 +68,14 @@ final class SpaceRoomPreviewViewController: UIViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
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,42 +135,85 @@ final class SpaceRoomPreviewViewController: UIViewController {
|
||||
self.update(theme: ThemeService.shared().theme)
|
||||
}
|
||||
|
||||
private func setupView() {
|
||||
self.titleLabel.text = roomInfo.displayName
|
||||
|
||||
self.spaceTagView.layer.masksToBounds = true
|
||||
self.spaceTagView.layer.cornerRadius = 2
|
||||
self.spaceTagView.isHidden = roomInfo.roomType != .space
|
||||
self.spaceTagLabel.text = VectorL10n.spaceTag
|
||||
private func renderLoaded(with parameters: RoomContextPreviewLoadedParameters) {
|
||||
self.titleLabel.text = parameters.displayName
|
||||
|
||||
self.avatarView.isHidden = roomInfo.roomType == .space
|
||||
self.spaceAvatarView.isHidden = roomInfo.roomType != .space
|
||||
self.spaceTagView.isHidden = parameters.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)
|
||||
}
|
||||
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 {
|
||||
if self.topicLabel.text.isEmptyOrNil {
|
||||
return self.topicLabel.frame.minY
|
||||
}
|
||||
|
||||
|
||||
let topicHeight = self.topicLabel.sizeThatFits(CGSize(width: width - self.topicLabel.frame.minX * 2, height: 0)).height
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
import CommonKit
|
||||
|
||||
final class EnterNewRoomDetailsViewController: UIViewController {
|
||||
|
||||
@@ -47,7 +48,9 @@ final class EnterNewRoomDetailsViewController: UIViewController {
|
||||
private var theme: Theme!
|
||||
private var keyboardAvoider: KeyboardAvoider?
|
||||
private var errorPresenter: MXKErrorPresentation!
|
||||
private var activityPresenter: ActivityIndicatorPresenter!
|
||||
private var userIndicatorPresenter: UserIndicatorTypePresenterProtocol!
|
||||
private var loadingIndicator: UserIndicator?
|
||||
|
||||
private lazy var createBarButtonItem: MXKBarButtonItem = {
|
||||
let title: String
|
||||
switch viewModel.actionType {
|
||||
@@ -262,7 +265,7 @@ final class EnterNewRoomDetailsViewController: UIViewController {
|
||||
|
||||
self.setupViews()
|
||||
self.keyboardAvoider = KeyboardAvoider(scrollViewContainerView: self.view, scrollView: self.mainTableView)
|
||||
self.activityPresenter = ActivityIndicatorPresenter()
|
||||
self.userIndicatorPresenter = UserIndicatorTypePresenter(presentingViewController: self)
|
||||
self.errorPresenter = MXKErrorAlertPresentation()
|
||||
|
||||
self.registerThemeServiceDidChangeThemeNotification()
|
||||
@@ -352,11 +355,11 @@ final class EnterNewRoomDetailsViewController: UIViewController {
|
||||
}
|
||||
|
||||
private func renderLoading() {
|
||||
self.activityPresenter.presentActivityIndicator(on: self.view, animated: true)
|
||||
loadingIndicator = userIndicatorPresenter.present(.loading(label: VectorL10n.createRoomProcessing, isInteractionBlocking: true))
|
||||
}
|
||||
|
||||
private func render(error: Error) {
|
||||
self.activityPresenter.removeCurrentActivityIndicator(animated: true)
|
||||
loadingIndicator = nil
|
||||
self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType {
|
||||
fatalError("[EnterNewRoomDetailsViewModel] createRoom: room name cannot be nil.")
|
||||
}
|
||||
|
||||
viewState = .loading
|
||||
currentOperation = session.createRoom(
|
||||
withName: roomName,
|
||||
joinRule: roomCreationParameters.joinRule,
|
||||
@@ -125,6 +126,8 @@ final class EnterNewRoomDetailsViewModel: EnterNewRoomDetailsViewModelType {
|
||||
completion: { response in
|
||||
switch response {
|
||||
case .success(let room):
|
||||
self.viewState = .loaded
|
||||
|
||||
if let parentSpace = self.parentSpace {
|
||||
self.add(room, to: parentSpace)
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixSDK
|
||||
|
||||
@objc extension MXRoomAliasResolution {
|
||||
|
||||
/// Deeplink fragment using a room identifier and a list of servers aware of this identifier
|
||||
///
|
||||
/// For more details see
|
||||
/// https://github.com/matrix-org/matrix-spec-proposals/blob/old_master/proposals/1704-matrix.to-permalinks.md
|
||||
var deeplinkFragment: String? {
|
||||
guard let roomId = roomId else {
|
||||
MXLog.debug("[MXRoomAliasResolution]: Missing room identifier")
|
||||
return nil
|
||||
}
|
||||
|
||||
return MXTools.encodeURIComponent(
|
||||
fragment(for: roomId)
|
||||
)
|
||||
}
|
||||
|
||||
private func fragment(for roomId: String) -> String {
|
||||
guard let servers = servers, !servers.isEmpty else {
|
||||
return roomId
|
||||
}
|
||||
return roomId + "?via=" + servers.joined(separator: "&via=")
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ class UniversalLinkParameters: NSObject {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The unprocessed the universal link URL
|
||||
/// The unprocessed universal link URL
|
||||
let universalLinkURL: URL
|
||||
|
||||
/// The fragment part of the universal link
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -622,111 +622,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
|
||||
@@ -863,50 +758,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
|
||||
@@ -1034,4 +885,38 @@
|
||||
}];
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
[animator addCompletion:^{
|
||||
[self showRoomWithRoomId:roomId inMatrixSession:self.mainSession];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -19,11 +19,15 @@ import Foundation
|
||||
#if canImport(JitsiMeetSDK)
|
||||
import JitsiMeetSDK
|
||||
|
||||
enum JitsiServiceError: Error {
|
||||
enum JitsiServiceError: LocalizedError {
|
||||
case widgetContentCreationFailed
|
||||
case emptyResponse
|
||||
case noWellKnown
|
||||
case unknown
|
||||
|
||||
var errorDescription: String? {
|
||||
return VectorL10n.callJitsiUnableToStart
|
||||
}
|
||||
}
|
||||
|
||||
private enum HTTPStatusCodes {
|
||||
@@ -143,32 +147,31 @@ final class JitsiService: NSObject {
|
||||
}
|
||||
|
||||
return self.getWellKnown(for: jitsiServerURL) { (result) in
|
||||
var continueOperation: Bool = false
|
||||
var authType: JitsiAuthenticationType?
|
||||
|
||||
func continueOperation(authType: JitsiAuthenticationType?) {
|
||||
guard let widgetContent = self.createJitsiWidgetContent(serverDomain: serverDomain,
|
||||
authenticationType: authType,
|
||||
roomID: roomID,
|
||||
isAudioOnly: isAudioOnly)
|
||||
else {
|
||||
failure(JitsiServiceError.widgetContentCreationFailed)
|
||||
return
|
||||
}
|
||||
|
||||
success(widgetContent)
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success(let jitsiWellKnown):
|
||||
authType = jitsiWellKnown.authenticationType
|
||||
continueOperation = true
|
||||
continueOperation(authType: jitsiWellKnown.authenticationType)
|
||||
case .failure(let error):
|
||||
MXLog.debug("[JitsiService] Fail to get Jitsi Well Known with error: \(error)")
|
||||
if let error = error as? JitsiServiceError, error == .noWellKnown {
|
||||
// no well-known, continue with no auth
|
||||
continueOperation = true
|
||||
continueOperation(authType: nil)
|
||||
} else {
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
|
||||
if continueOperation,
|
||||
let widgetContent = self.createJitsiWidgetContent(serverDomain: serverDomain,
|
||||
authenticationType: authType,
|
||||
roomID: roomID,
|
||||
isAudioOnly: isAudioOnly) {
|
||||
success(widgetContent)
|
||||
} else {
|
||||
failure(JitsiServiceError.widgetContentCreationFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
#import "MXKRoomInputToolbarView.h"
|
||||
#import "MXKImageView.h"
|
||||
#import "MXKRoomBubbleCellData.h"
|
||||
#import "UserIndicatorCancel.h"
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||