diff --git a/CHANGES.rst b/CHANGES.rst
index 6d4065d85..ccb521a38 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,32 @@
+Changes in 1.3.7 (2021-05-12)
+=================================================
+
+✨ Features
+ *
+
+🙌 Improvements
+ * NSE: Add logs for notification delay.
+ * Templates: Update bridge presenter template to auto-implement iOS 13 pull-down gesture.
+
+🐛 Bugfix
+ * NSE: Fixes to avoid PushKit crashes (#4269).
+ * Handle pull-down gesture for reactions history view (#4293).
+
+⚠️ API Changes
+ *
+
+🗣 Translations
+ *
+
+🧱 Build
+ *
+
+Others
+ *
+
+Improvements:
+ * Upgrade MatrixKit version ([v0.14.12](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.14.12)).
+
Changes in 1.3.6 (2021-05-07)
=================================================
@@ -14,6 +43,9 @@ Changes in 1.3.6 (2021-05-07)
* Add Jitsi widget remove banner for privileged users.
* Update "Jump to unread" banner to a pill style button.
* CallVC: Add transfer button.
+ * Spaces: Hide spaces from room list and home but keep space invites (#4252).
+ * Spaces: Show space invites and advertise that they are not available (#4277).
+ * Advertise that spaces are not available when tapping on a space link or a space invite (#4279).
🐛 Bugfix
* RoomVC: Avoid navigation to integration management using integration popup with settings set to integration disabled (#4261).
diff --git a/Config/AppIdentifiers.xcconfig b/Config/AppIdentifiers.xcconfig
index d8a4e9d4d..83f452d96 100644
--- a/Config/AppIdentifiers.xcconfig
+++ b/Config/AppIdentifiers.xcconfig
@@ -22,8 +22,8 @@ APPLICATION_GROUP_IDENTIFIER = group.im.vector
APPLICATION_SCHEME = element
// Version
-MARKETING_VERSION = 1.3.6
-CURRENT_PROJECT_VERSION = 1.3.6
+MARKETING_VERSION = 1.3.7
+CURRENT_PROJECT_VERSION = 1.3.7
// Team
diff --git a/Podfile b/Podfile
index faff847c4..30d892be5 100644
--- a/Podfile
+++ b/Podfile
@@ -11,7 +11,7 @@ use_frameworks!
# - `{ {kit spec hash} => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for each 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
-$matrixKitVersion = '= 0.14.11'
+$matrixKitVersion = '= 0.14.12'
# $matrixKitVersion = :local
# $matrixKitVersion = {'develop' => 'develop'}
diff --git a/Podfile.lock b/Podfile.lock
index 6d2878f14..986373e53 100644
--- a/Podfile.lock
+++ b/Podfile.lock
@@ -55,28 +55,28 @@ PODS:
- MatomoTracker (7.4.1):
- MatomoTracker/Core (= 7.4.1)
- MatomoTracker/Core (7.4.1)
- - MatrixKit (0.14.11):
+ - MatrixKit (0.14.12):
- Down (~> 0.10.0)
- DTCoreText (~> 1.6.25)
- HPGrowingTextView (~> 1.1)
- libPhoneNumber-iOS (~> 0.9.13)
- - MatrixKit/Core (= 0.14.11)
- - MatrixSDK (= 0.18.11)
- - MatrixKit/Core (0.14.11):
+ - MatrixKit/Core (= 0.14.12)
+ - MatrixSDK (= 0.18.12)
+ - MatrixKit/Core (0.14.12):
- Down (~> 0.10.0)
- DTCoreText (~> 1.6.25)
- HPGrowingTextView (~> 1.1)
- libPhoneNumber-iOS (~> 0.9.13)
- - MatrixSDK (= 0.18.11)
- - MatrixSDK (0.18.11):
- - MatrixSDK/Core (= 0.18.11)
- - MatrixSDK/Core (0.18.11):
+ - MatrixSDK (= 0.18.12)
+ - MatrixSDK (0.18.12):
+ - MatrixSDK/Core (= 0.18.12)
+ - MatrixSDK/Core (0.18.12):
- AFNetworking (~> 4.0.0)
- GZIP (~> 1.3.0)
- libbase58 (~> 0.1.4)
- OLMKit (~> 3.2.2)
- Realm (= 10.7.2)
- - MatrixSDK/JingleCallStack (0.18.11):
+ - MatrixSDK/JingleCallStack (0.18.12):
- JitsiMeetSDK (= 3.2.0)
- MatrixSDK/Core
- OLMKit (3.2.2):
@@ -115,7 +115,7 @@ DEPENDENCIES:
- KeychainAccess (~> 4.2.2)
- KTCenterFlowLayout (~> 1.3.1)
- MatomoTracker (~> 7.4.1)
- - MatrixKit (= 0.14.11)
+ - MatrixKit (= 0.14.12)
- MatrixSDK
- MatrixSDK/JingleCallStack
- OLMKit
@@ -186,8 +186,8 @@ SPEC CHECKSUMS:
LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d
Logging: beeb016c9c80cf77042d62e83495816847ef108b
MatomoTracker: 24a846c9d3aa76933183fe9d47fd62c9efa863fb
- MatrixKit: b4427e22aed3ae4a3ef5175bb017fc2b9786269c
- MatrixSDK: f3cecb8e8ca7624dc52940dfae95ae8b81ebee1a
+ MatrixKit: 7e372f21d63bbc489d64f493e47544193cd5635c
+ MatrixSDK: f72f413738e0f03892d9127c6b0b96cd8642abe1
OLMKit: 20d1c564033a1ae7148f8f599378d4c798363905
ReadMoreTextView: 19147adf93abce6d7271e14031a00303fe28720d
Realm: e523da9ade306c5ae87e85dc09fdef148d3e1cc1
@@ -199,6 +199,6 @@ SPEC CHECKSUMS:
zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
-PODFILE CHECKSUM: eafd5b002866b4fb5d6814a0b07f1c3d99c5744a
+PODFILE CHECKSUM: 150af66ac75238a58b5665592850c990348adf31
-COCOAPODS: 1.10.1
+COCOAPODS: 1.10.0
diff --git a/Riot/Assets/Images.xcassets/Spaces/Contents.json b/Riot/Assets/Images.xcassets/Spaces/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Spaces/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/Contents.json
new file mode 100644
index 000000000..89de3f057
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "feature_unavaible_artwork.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "feature_unavaible_artwork@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "feature_unavaible_artwork@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork.png
new file mode 100644
index 000000000..bd404a5e6
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork.png differ
diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@2x.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@2x.png
new file mode 100644
index 000000000..e6f2bf781
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@2x.png differ
diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@3x.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@3x.png
new file mode 100644
index 000000000..12be231e7
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork.imageset/feature_unavaible_artwork@3x.png differ
diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/Contents.json b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/Contents.json
new file mode 100644
index 000000000..3de7744a6
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "feature_unavaible_artwork_dark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "feature_unavaible_artwork_dark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "feature_unavaible_artwork_dark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark.png
new file mode 100644
index 000000000..f90898c72
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark.png differ
diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@2x.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@2x.png
new file mode 100644
index 000000000..fb24831b7
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@2x.png differ
diff --git a/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@3x.png b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@3x.png
new file mode 100644
index 000000000..bdda22aa4
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Spaces/feature_unavaible_artwork_dark.imageset/feature_unavaible_artwork_dark@3x.png differ
diff --git a/Riot/Assets/ar.lproj/Localizable.strings b/Riot/Assets/ar.lproj/Localizable.strings
index 05c668e49..662dc870d 100644
--- a/Riot/Assets/ar.lproj/Localizable.strings
+++ b/Riot/Assets/ar.lproj/Localizable.strings
@@ -111,3 +111,9 @@
/* New message from a specific person, not referencing a room */
"MSG_FROM_USER" = "لَقَد أرسَلَ %@ رِسالة";
+
+/* Group call from user, CallKit caller name */
+"GROUP_CALL_FROM_USER" = "%@ (مُكالَمَةٌ جَماعِيَّة)";
+
+/* A user added a Jitsi call to a room */
+"GROUP_CALL_STARTED" = "بَدَأت مُكالَمَةٌ جَماعِيَّة";
diff --git a/Riot/Assets/ar.lproj/Vector.strings b/Riot/Assets/ar.lproj/Vector.strings
index 310cc5f70..741e834b1 100644
--- a/Riot/Assets/ar.lproj/Vector.strings
+++ b/Riot/Assets/ar.lproj/Vector.strings
@@ -137,3 +137,10 @@
"auth_reset_password_success_message" = "لَقَد تمَّ إعادَةُ ضَبطِ كَلِمَةِ المُرُور الخَّاصَّةِ بِك.\n\nلَقَد تمَّ تَسجِيلُ خُرُوجِك مِن جَميعِ الجَلَسات وَلَن تَستَلِمَ بَعد الآن دَفعَ الإِشعَارات. لِإعادَةِ تَفعِيل الإِشعَارات، أعِد تَسجِيلَ الدُّخول عَلَى كُلِّ جِهاز.";
"auth_reset_password_error_not_found" = "لَا يَبدو أنَّ عُنوان البَريد الإلِكتُرونيّ الخَّاصِّ بِك مُقتَرِنٌ بِمُعَرِّف Matrix عَلَى الخادِمِ الرَّئيس هَذَا.";
"auth_reset_password_error_unauthorized" = "فَشَلَ التَّحَقُق مِن عُنوان البَريد الإلِكتُرونيّ: تَأكَّد مِن نَقرِكَ عَلَى الرَّابِط الَّذي فِي البَريد الإلِكتُرونيّ";
+"callbar_only_single_active_group" = "اُنقُر لِلاِنضِمَامِ إلَى المُكالَمَةِ الجَماعِيَّة (%@)";
+"auth_softlogout_clear_data_sign_out_title" = "هل أنت واثق؟";
+"auth_softlogout_clear_data_button" = "مَحوُ كَافَّةِ البَيَانَات";
+"auth_softlogout_clear_data_message_2" = "اِمحِيهَا إذَا اِنتَهَيتَ مِن اِستِخدَامِ هَذَا الجِّهاز، أو إذَا أردتَ تَسجيلَ الدُّخُولِ إلَى حِسَابٍ آخَر.";
+"auth_softlogout_clear_data_message_1" = "تَحذير: بَيَانَاتُكَ الشَّخصِيَّة (بِمَا فِي ذَلِكَ مَفاتيحُ التَّعمِيَة) لَا تَزَالُ مُخَزَنَةً عَلَى هَذَا الجِّهاز.";
+"auth_softlogout_clear_data" = "مَحوُ البَيَانَاتِ الشَّخصِيَّة";
+"auth_softlogout_recover_encryption_keys" = "سَجَّل الدُّخُول لِاِستِعادَة مَفاتيح التَّعمِيَة المُخَزَّنَة حَصرِيًّا عَلَى هَذَا الجِّهاز. أنتَ بِحاجةٍ لَهُم لِقِراءةِ جَمِيعِ رَسائِلكَ الآمِنَة عَلَى أيِّ جِهاز.";
diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings
index 277d820ce..dc0cbc884 100644
--- a/Riot/Assets/en.lproj/Vector.strings
+++ b/Riot/Assets/en.lproj/Vector.strings
@@ -1632,3 +1632,14 @@ Tap the + to start adding people.";
"room_intro_cell_information_dm_sentence2" = "Only the two of you are in this conversation, no one else can join.";
"room_intro_cell_information_multiple_dm_sentence2" = "Only you are in this conversation, unless any of you invites someone to join.";
+
+// Mark: - Spaces
+
+"space_feature_unavailable_title" = "Spaces aren’t here yet";
+"space_feature_unavailable_subtitle" = "Spaces aren't on iOS yet, but you can use them now on Web and Desktop";
+"space_feature_unavailable_information" = "Spaces are a new way to group rooms and people.\n\nThey’ll be here soon. For now, if you join one on another platform, you will be able to access any rooms you join here.";
+
+"space_beta_announce_badge" = "BETA";
+"space_beta_announce_title" = "Spaces are coming soon";
+"space_beta_announce_subtitle" = "The new version of communities";
+"space_beta_announce_information" = "Spaces are a new way to group rooms and people. They’re not on iOS yet, but you can use them now on Web and Desktop.";
diff --git a/Riot/Assets/et.lproj/Localizable.strings b/Riot/Assets/et.lproj/Localizable.strings
index 8fe3f8730..544179165 100644
--- a/Riot/Assets/et.lproj/Localizable.strings
+++ b/Riot/Assets/et.lproj/Localizable.strings
@@ -62,3 +62,9 @@
/* Incoming named video conference invite from a specific person */
"VIDEO_CONF_NAMED_FROM_USER" = "Video rühmakõne kasutajalt %@: „%@“";
"MESSAGE_PROTECTED" = "Uus sõnum";
+
+/* Group call from user, CallKit caller name */
+"GROUP_CALL_FROM_USER" = "%@ (rühmakõne)";
+
+/* A user added a Jitsi call to a room */
+"GROUP_CALL_STARTED" = "Rühmakõne algas";
diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings
index 01e5535cd..e02bf3a7c 100644
--- a/Riot/Assets/et.lproj/Vector.strings
+++ b/Riot/Assets/et.lproj/Vector.strings
@@ -115,7 +115,7 @@
"room_participants_security_information_room_not_encrypted" = "See jututuba ei ole läbivalt krüptitud.";
"room_participants_security_information_room_encrypted" = "Sõnumid siin jututoas kasutavad läbivat krüptimist.\n\nSinu sõnumid on turvatud ning ainult sinul ja saaja(te)l on unikaalsed võtmed selliste sõnumite lugemiseks.";
// Chat
-"room_jump_to_first_unread" = "Mine esimese lugemata sõnumi juurde";
+"room_jump_to_first_unread" = "Mine lugemata sõnumite juurde";
// Room Details
"room_details_title" = "Jututoa üksikasjad";
"room_details_advanced_enable_e2e_encryption" = "Võta krüptimine kasutusele (hoiatus: seda ei saa hiljem enam välja lülitada!)";
@@ -1269,8 +1269,8 @@
"call_actions_unhold" = "Jätka";
"event_formatter_call_back" = "Helista tagasi";
"event_formatter_call_you_declined" = "Sina keeldusid sellest kõnest";
-"event_formatter_call_you_currently_in" = "Sul on parasjagu see kõne pooleli";
-"event_formatter_call_has_ended" = "See kõne on lõppenud";
+"event_formatter_call_you_currently_in" = "Kõne on käsil";
+"event_formatter_call_has_ended" = "%@ kõne on lõppenud";
"event_formatter_call_video" = "Videokõne";
"event_formatter_call_voice" = "Häälkõne";
"room_open_dialpad" = "Numbriklahvistik";
@@ -1286,10 +1286,29 @@
"callbar_active_and_single_paused" = "Käsil on üks kõne (%@) · üks kõne on ootel";
// Call Bar
-"callbar_only_single_active" = "Kõne on käsil (%@)";
+"callbar_only_single_active" = "Kõne juurde tagasipöördumiseks puuduta (%@)";
"room_details_integrations" = "Lõiminguid";
"room_details_search" = "Otsi jututoast";
"room_multiple_typing_notification" = "%@ ja muud kasutajad";
"room_accessibility_video_call" = "Videokõne";
"room_message_replying_to" = "Vastan sõnumile %@";
"room_message_editing" = "Muudan sõnumit";
+"room_no_privileges_to_create_group_call" = "Kõne alustamiseks peaksid sa olema haldaja või moderaatori õigustes.";
+"event_formatter_group_call_incoming" = "%@ kõnes %@";
+"event_formatter_group_call_leave" = "Lahku";
+"event_formatter_group_call_join" = "Liitu";
+"event_formatter_group_call" = "Rühmakõne";
+"event_formatter_call_end_call" = "Lõpeta kõne";
+"event_formatter_call_retry" = "Proovi uuesti";
+"event_formatter_call_answer" = "Vasta";
+"event_formatter_call_decline" = "Keeldu";
+"event_formatter_call_connection_failed" = "Ühendus ebaõnnestus";
+"event_formatter_call_you_missed" = "Sa ei märganud seda kõnet";
+"event_formatter_call_ringing" = "Helistan…";
+"event_formatter_call_connecting" = "Kõne on ühendamisel…";
+"settings_labs_enable_ringing_for_group_calls" = "Rühmakõnede helin";
+"room_join_group_call" = "Liitu";
+
+// Chat
+"room_slide_to_end_group_call" = "Viipa kõne lõpetamiseks kõigi jaoks";
+"callbar_only_single_active_group" = "Rühmakõnega liitumiseks puuduta (%@)";
diff --git a/Riot/Assets/fr.lproj/Localizable.strings b/Riot/Assets/fr.lproj/Localizable.strings
index c8188f312..41d926398 100644
--- a/Riot/Assets/fr.lproj/Localizable.strings
+++ b/Riot/Assets/fr.lproj/Localizable.strings
@@ -62,3 +62,9 @@
/* New message indicator on a room */
"MESSAGE_IN_X" = "Message dans %@";
"MESSAGE_PROTECTED" = "Nouveau message";
+
+/* Group call from user, CallKit caller name */
+"GROUP_CALL_FROM_USER" = "%@ (appel de groupe)";
+
+/* A user added a Jitsi call to a room */
+"GROUP_CALL_STARTED" = "L’appel de groupe a démarré";
diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings
index 97118828c..0583b7f94 100644
--- a/Riot/Assets/fr.lproj/Vector.strings
+++ b/Riot/Assets/fr.lproj/Vector.strings
@@ -1301,7 +1301,7 @@
"secrets_reset_title" = "Tout réinitialiser";
"device_verification_self_verify_wait_recover_secrets_checking_availability" = "Contrôle des autres fonctionnalités de vérification…";
-"event_formatter_call_you_currently_in" = "Vous êtes actuellement dans cet appel";
+"event_formatter_call_you_currently_in" = "Appel en cours";
"rooms_empty_view_information" = "Les salons sont utiles pour toute conversation de groupe, privée ou publique. Appuyez sur « + » pour trouver des salons existants, ou en créer de nouveaux.";
"rooms_empty_view_title" = "Salons";
"people_empty_view_information" = "Communiquez de manière sécurisée avec n’importe qui. Appuyez sur « + » pour commencer à ajouter des personnes.";
@@ -1318,7 +1318,7 @@
"call_actions_unhold" = "Reprendre";
"event_formatter_call_back" = "Rappeler";
"event_formatter_call_you_declined" = "Vous avez refusé cet appel";
-"event_formatter_call_has_ended" = "L’appel est terminé";
+"event_formatter_call_has_ended" = "Appel %@ terminé";
"event_formatter_call_video" = "Appel vidéo";
"event_formatter_call_voice" = "Appel audio";
"room_details_advanced_e2e_encryption_disabled_for_dm" = "Le chiffrement n’est pas activé ici.";
@@ -1358,7 +1358,7 @@
"callbar_active_and_single_paused" = "1 appel en cours (%@) · 1 appel en attente";
// Call Bar
-"callbar_only_single_active" = "Appel en cours (%@)";
+"callbar_only_single_active" = "Appuyez pour revenir à l’appel (%@)";
"less" = "Moins";
"more" = "Plus";
"switch" = "Basculer";
@@ -1370,3 +1370,22 @@
"room_accessibility_video_call" = "Appel vidéo";
"room_message_replying_to" = "Réponse à %@";
"room_message_editing" = "Modification";
+"event_formatter_group_call_incoming" = "%@ dans %@";
+"event_formatter_group_call_leave" = "Partir";
+"event_formatter_group_call_join" = "Rejoindre";
+"event_formatter_group_call" = "Appel de groupe";
+"event_formatter_call_end_call" = "Raccrocher";
+"event_formatter_call_retry" = "Réessayer";
+"event_formatter_call_answer" = "Décrocher";
+"event_formatter_call_decline" = "Refuser";
+"event_formatter_call_connection_failed" = "Échec de connexion";
+"event_formatter_call_you_missed" = "Vous avez manqué cet appel";
+"event_formatter_call_ringing" = "Sonnerie…";
+"event_formatter_call_connecting" = "Connexion…";
+"settings_labs_enable_ringing_for_group_calls" = "Sonnerie pour les appels de groupe";
+"room_no_privileges_to_create_group_call" = "Vous devez être administrateur ou modérateur pour démarrer un appel.";
+"room_join_group_call" = "Rejoindre";
+
+// Chat
+"room_slide_to_end_group_call" = "Faites glisser pour mettre fin à l’appel pour tout le monde";
+"callbar_only_single_active_group" = "Appuyez pour rejoindre l’appel de groupe (%@)";
diff --git a/Riot/Assets/hu.lproj/Localizable.strings b/Riot/Assets/hu.lproj/Localizable.strings
index d12a8cca6..6b1e66e32 100644
--- a/Riot/Assets/hu.lproj/Localizable.strings
+++ b/Riot/Assets/hu.lproj/Localizable.strings
@@ -62,3 +62,9 @@
/* New message indicator on a room */
"MESSAGE_IN_X" = "Üzenet itt: %@";
"MESSAGE_PROTECTED" = "Új üzenet";
+
+/* Group call from user, CallKit caller name */
+"GROUP_CALL_FROM_USER" = "%@ (Csoportos hívás)";
+
+/* A user added a Jitsi call to a room */
+"GROUP_CALL_STARTED" = "Csoportos hívás elkezdődött";
diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings
index afb1f59a4..7521dd6c4 100644
--- a/Riot/Assets/hu.lproj/Vector.strings
+++ b/Riot/Assets/hu.lproj/Vector.strings
@@ -1332,8 +1332,8 @@
"call_actions_unhold" = "Folytatás";
"event_formatter_call_back" = "Visszahívás";
"event_formatter_call_you_declined" = "Elutasította ezt a hívást";
-"event_formatter_call_you_currently_in" = "Jelenleg ebben a hívásban van";
-"event_formatter_call_has_ended" = "Hívás befejeződött";
+"event_formatter_call_you_currently_in" = "Aktív hívás";
+"event_formatter_call_has_ended" = "Befejeződött: %@";
"event_formatter_call_video" = "Videóhívás";
"event_formatter_call_voice" = "Hang hívás";
"room_open_dialpad" = "Tárcsázó számlap";
@@ -1349,10 +1349,29 @@
"callbar_active_and_single_paused" = "1 aktív hívás (%@) · 1 hívás tartásban";
// Call Bar
-"callbar_only_single_active" = "Aktív hívás (%@)";
+"callbar_only_single_active" = "Koppints a hívásba való visszatéréshez (%@)";
"room_details_integrations" = "Integrációk";
"room_details_search" = "Szoba keresése";
"room_multiple_typing_notification" = "%@ és mások";
"room_accessibility_video_call" = "Videohívás";
"room_message_replying_to" = "Válasz erre: %@";
"room_message_editing" = "Szerkesztés";
+"event_formatter_group_call_incoming" = "%@ itt: %@";
+"event_formatter_group_call_leave" = "Elhagy";
+"event_formatter_group_call_join" = "Csatlakozás";
+"event_formatter_group_call" = "Csoportos hívás";
+"event_formatter_call_end_call" = "Hívás befejezése";
+"event_formatter_call_retry" = "Újra";
+"event_formatter_call_answer" = "Válaszol";
+"event_formatter_call_decline" = "Elutasít";
+"event_formatter_call_connection_failed" = "Kapcsolódás sikertelen";
+"event_formatter_call_you_missed" = "Elmulasztottad ezt a hívást";
+"event_formatter_call_ringing" = "Hívás…";
+"event_formatter_call_connecting" = "Kapcsolás…";
+"settings_labs_enable_ringing_for_group_calls" = "Csengetés csoportos hívásokhoz";
+"room_no_privileges_to_create_group_call" = "Adminisztrátornak vagy moderátornak kell lenned a hívás indításához.";
+"room_join_group_call" = "Csatlakozás";
+
+// Chat
+"room_slide_to_end_group_call" = "Húzd el a hívás befejezéséhez mindenki számára";
+"callbar_only_single_active_group" = "Koppints a csoporthívásba való belépéshez (%@)";
diff --git a/Riot/Assets/it.lproj/Localizable.strings b/Riot/Assets/it.lproj/Localizable.strings
index 526b40ba6..f0fc8f229 100644
--- a/Riot/Assets/it.lproj/Localizable.strings
+++ b/Riot/Assets/it.lproj/Localizable.strings
@@ -62,3 +62,9 @@
/* New message indicator on a room */
"MESSAGE_IN_X" = "Messaggio in %@";
"MESSAGE_PROTECTED" = "Nuovo messaggio";
+
+/* Group call from user, CallKit caller name */
+"GROUP_CALL_FROM_USER" = "%@ (chiamata di gruppo)";
+
+/* A user added a Jitsi call to a room */
+"GROUP_CALL_STARTED" = "Chiamata di gruppo iniziata";
diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings
index 37edfa354..30daa86f2 100644
--- a/Riot/Assets/it.lproj/Vector.strings
+++ b/Riot/Assets/it.lproj/Vector.strings
@@ -201,7 +201,7 @@
"room_participants_action_start_video_call" = "Nuova videochiamata";
"room_participants_action_mention" = "Citazione";
// Chat
-"room_jump_to_first_unread" = "Vai al primo messaggio non letto";
+"room_jump_to_first_unread" = "Vai ai non letti";
"room_new_message_notification" = "%d nuovo messaggio";
"room_new_messages_notification" = "%d nuovi messaggi";
"room_one_user_is_typing" = "%@ sta scrivendo…";
@@ -1302,8 +1302,8 @@
"call_actions_unhold" = "Riprendi";
"event_formatter_call_back" = "Richiama";
"event_formatter_call_you_declined" = "Hai rifiutato questa chiamata";
-"event_formatter_call_you_currently_in" = "Sei attualmente in questa chiamata";
-"event_formatter_call_has_ended" = "Questa chiamata è terminata";
+"event_formatter_call_you_currently_in" = "Chiamata attiva";
+"event_formatter_call_has_ended" = "Terminato %@";
"event_formatter_call_video" = "Videochiamata";
"event_formatter_call_voice" = "Telefonata";
"settings_show_NSFW_public_rooms" = "Mostra stanze pubbliche per adulti";
@@ -1320,10 +1320,29 @@
"callbar_active_and_single_paused" = "1 chiamata attiva (%@) · 1 chiamata in pausa";
// Call Bar
-"callbar_only_single_active" = "Chiamata attiva (%@)";
+"callbar_only_single_active" = "Tocca per tornare alla chiamata (%@)";
"room_details_integrations" = "Integrazioni";
"room_details_search" = "Cerca stanza";
"room_multiple_typing_notification" = "%@ e altri";
"room_accessibility_video_call" = "Videochiamata";
"room_message_editing" = "Modifica";
"room_message_replying_to" = "Risposta a %@";
+"event_formatter_group_call_incoming" = "%@ in %@";
+"event_formatter_group_call_leave" = "Esci";
+"event_formatter_group_call_join" = "Entra";
+"event_formatter_group_call" = "Chiamata di gruppo";
+"event_formatter_call_end_call" = "Termina chiamata";
+"event_formatter_call_retry" = "Riprova";
+"event_formatter_call_answer" = "Rispondi";
+"event_formatter_call_decline" = "Rifiuta";
+"event_formatter_call_connection_failed" = "Connessione fallita";
+"event_formatter_call_you_missed" = "Hai perso questa chiamata";
+"event_formatter_call_ringing" = "Sta squillando…";
+"event_formatter_call_connecting" = "In connessione…";
+"settings_labs_enable_ringing_for_group_calls" = "Squilla per chiamate di gruppo";
+"room_no_privileges_to_create_group_call" = "Devi essere un amministratore o un moderatore per iniziare una chiamata.";
+"room_join_group_call" = "Entra";
+
+// Chat
+"room_slide_to_end_group_call" = "Scorri per terminare la chiamata per tutti";
+"callbar_only_single_active_group" = "Tocca per unirti alla chiamata di gruppo (%@)";
diff --git a/Riot/Assets/nb-NO.lproj/Vector.strings b/Riot/Assets/nb-NO.lproj/Vector.strings
index 482d7213a..ae04abd14 100644
--- a/Riot/Assets/nb-NO.lproj/Vector.strings
+++ b/Riot/Assets/nb-NO.lproj/Vector.strings
@@ -860,3 +860,16 @@
"settings_flair" = "Vis brukervalg hvor tillat";
"settings_on_denied_notification" = "Varsler er ikke tillat for %@, vennligst tillat dem i enhetens innstillinger";
"settings_pin_rooms_with_missed_notif" = "Fest rom med tapte varsler";
+"room_info_list_several_members" = "% @ medlemmer";
+"pin_protection_not_allowed_pin" = "Av sikkerhetsgrunner er denne PIN-koden ikke tilgjengelig. Prøv en annen PIN-kode";
+"secrets_setup_recovery_passphrase_information" = "Skriv inn et sikkerhetsfrase bare du kjenner, brukt til å sikre hemmeligheter på serveren din.";
+"secrets_recovery_with_passphrase_lost_passphrase_action_part2" = "bruk gjenopprettingsnøkkelen din";
+"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "bruk gjenopprettingsnøkkelen";
+"room_details_access_section_for_dm" = "Hvem har til gang til dette?";
+"identity_server_settings_place_holder" = "Skriv inn en identitetsserver";
+"identity_server_settings_description" = "Du bruker for øyeblikket %@ for å finne å bli funnet av dine eksisterende kontakter.";
+"security_settings_crosssigning_info_exists" = "Din konto har en kryss-signeringsidentitet, men den er ikke klarert av den økten. Fullfør klarering for denne økten.";
+"security_settings_crypto_sessions_loading" = "Laster sesjoner…";
+"settings_discovery_no_identity_server" = "Du bruker ikke for øyeblikket en identitetsserver. Legg til en for at dine eksisterende kontakter skal kunne finne deg.";
+"settings_unignore_user" = "Vi alle meldinger fra %@?";
+"device_verification_cancelled" = "Den andre parten avbrøt verifiseringen";
diff --git a/Riot/Assets/nl.lproj/Localizable.strings b/Riot/Assets/nl.lproj/Localizable.strings
index aa136cda1..c64b03092 100644
--- a/Riot/Assets/nl.lproj/Localizable.strings
+++ b/Riot/Assets/nl.lproj/Localizable.strings
@@ -95,3 +95,9 @@
/* New message indicator on unknown room */
"MESSAGE" = "Bericht";
+
+/* A user added a Jitsi call to a room */
+"GROUP_CALL_STARTED" = "Groepsgesprek gestart";
+
+/* Group call from user, CallKit caller name */
+"GROUP_CALL_FROM_USER" = "%@ (groepsgesprek)";
diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings
index 87c8fb14f..cb3d872ce 100644
--- a/Riot/Assets/nl.lproj/Vector.strings
+++ b/Riot/Assets/nl.lproj/Vector.strings
@@ -194,7 +194,7 @@
"room_participants_action_start_video_call" = "Video-oproep beginnen";
"room_participants_action_mention" = "Vermelden";
// Chat
-"room_jump_to_first_unread" = "Spring naar het eerste ongelezen bericht";
+"room_jump_to_first_unread" = "Spring naar ongelezen";
"room_new_message_notification" = "%d nieuw bericht";
"room_new_messages_notification" = "%d nieuwe berichten";
"room_one_user_is_typing" = "%@ is aan het typen…";
@@ -1285,8 +1285,8 @@
"event_formatter_widget_added_by_you" = "U heeft deze widget toegevoegd: %@";
"event_formatter_call_back" = "Terugbellen";
"event_formatter_call_you_declined" = "U heeft de oproep afgewezen";
-"event_formatter_call_you_currently_in" = "U bent momenteel in deze oproep";
-"event_formatter_call_has_ended" = "Deze oproep is beëindigd";
+"event_formatter_call_you_currently_in" = "Actieve oproep";
+"event_formatter_call_has_ended" = "Beëindigd %@";
"event_formatter_call_video" = "Video-oproep";
"event_formatter_call_voice" = "Audio-oproep";
"room_details_advanced_e2e_encryption_disabled_for_dm" = "Versleuteling is hier niet ingeschakeld.";
@@ -1444,7 +1444,7 @@
"callbar_active_and_single_paused" = "1 oproep actief (%@) · 1 oproep in de wacht";
// Call Bar
-"callbar_only_single_active" = "Oproep actief (%@)";
+"callbar_only_single_active" = "Ga terug naar de oproep (%@)";
"less" = "Minder";
"more" = "Meer";
"switch" = "Wissel";
@@ -1456,3 +1456,22 @@
"room_accessibility_video_call" = "Video-oproep";
"room_message_replying_to" = "Antwoord aan %@";
"room_message_editing" = "Bewerken";
+"callbar_only_single_active_group" = "Klik om deel te nemen aan groepsgesprek (%@)";
+"event_formatter_group_call_incoming" = "%@ in %@";
+"event_formatter_group_call_leave" = "Verlaten";
+"event_formatter_group_call_join" = "Deelnemen";
+"event_formatter_group_call" = "Groepsgesprek";
+"event_formatter_call_end_call" = "Oproep beëindigen";
+"event_formatter_call_retry" = "Opnieuw";
+"event_formatter_call_answer" = "Opnemen";
+"event_formatter_call_decline" = "Weigeren";
+"event_formatter_call_connection_failed" = "Verbinding mislukt";
+"event_formatter_call_you_missed" = "U heeft deze oproep gemist";
+"event_formatter_call_ringing" = "Bellen…";
+"event_formatter_call_connecting" = "Verbinden…";
+"settings_labs_enable_ringing_for_group_calls" = "Audiomelding voor groepsgesprek";
+"room_no_privileges_to_create_group_call" = "U moet een beheerder of moderator zijn om een gesprek te starten.";
+"room_join_group_call" = "Deelnemen";
+
+// Chat
+"room_slide_to_end_group_call" = "Schuif om voor iedereen de oproep te beëindigen";
diff --git a/Riot/Assets/pl.lproj/Localizable.strings b/Riot/Assets/pl.lproj/Localizable.strings
index 4888159c7..2b707cf88 100644
--- a/Riot/Assets/pl.lproj/Localizable.strings
+++ b/Riot/Assets/pl.lproj/Localizable.strings
@@ -62,3 +62,9 @@
/* New message indicator on a room */
"MESSAGE_IN_X" = "Wiadomość w %@";
"MESSAGE_PROTECTED" = "Nowa Wiadomość";
+
+/* Group call from user, CallKit caller name */
+"GROUP_CALL_FROM_USER" = "%@ (Połączenie grupowe)";
+
+/* A user added a Jitsi call to a room */
+"GROUP_CALL_STARTED" = "Rozpoczęto połączenie grupowe";
diff --git a/Riot/Assets/pl.lproj/Vector.strings b/Riot/Assets/pl.lproj/Vector.strings
index c559c823d..c174a2c1f 100644
--- a/Riot/Assets/pl.lproj/Vector.strings
+++ b/Riot/Assets/pl.lproj/Vector.strings
@@ -6,7 +6,7 @@
"title_groups" = "Społeczności";
"warning" = "Ostrzeżenie";
// Actions
-"view" = "Wyświetl";
+"view" = "Podgląd";
"back" = "Powrót";
"continue" = "Kontynuuj";
"create" = "Utwórz";
@@ -373,7 +373,7 @@
"room_participants_unknown" = "Status nieznany";
"room_participants_action_section_other" = "Opcje";
// Chat
-"room_jump_to_first_unread" = "Przeskocz do pierwszej nieprzeczytanej wiadomości";
+"room_jump_to_first_unread" = "Przeskocz do nieprzeczytanych wiadomości";
"room_new_message_notification" = "%d nowa wiadomość";
"room_new_messages_notification" = "%d nowych wiadomości";
"room_one_user_is_typing" = "%@ pisze…";
@@ -1290,8 +1290,8 @@
"event_formatter_jitsi_widget_added_by_you" = "Dodałeś(-aś) konferencję VoIP";
"event_formatter_call_back" = "Oddzwoń";
"event_formatter_call_you_declined" = "Odrzuciłeś to połączenie";
-"event_formatter_call_you_currently_in" = "Bierzesz obecnie udział w tej rozmowie";
-"event_formatter_call_has_ended" = "Rozmowa zakończona";
+"event_formatter_call_you_currently_in" = "Aktywne połączenie";
+"event_formatter_call_has_ended" = "Zakończono %@";
"event_formatter_call_video" = "Połączenie Wideo";
"event_formatter_call_voice" = "Połączenie głosowe";
"room_details_advanced_e2e_encryption_disabled_for_dm" = "Szyfrowanie nie jest włączone w tym pokoju.";
@@ -1416,8 +1416,27 @@
"callbar_active_and_single_paused" = "1 aktywne połączenie (%@) · 1 wstrzymane połączenie";
// Call Bar
-"callbar_only_single_active" = "Aktywne połączenie (%@)";
+"callbar_only_single_active" = "Dotknij, aby powrócić do rozmowy (%@)";
"auth_email_is_required" = "Brak skonfigurowanego serwera tożsamości, nie możesz dodać adresu e-mail na wypadek konieczności zresetowania hasła.";
"auth_add_email_phone_message_2" = "Ustaw adres e-mail na wypadek konieczności przywracania dostępu do konta, dodatkowo adres ten lub numer telefonu może być wykorzystany przez Twoich znajomych do znalezienia Twojego profilu.";
"auth_add_phone_message_2" = "Ustaw numer telefonu, numer ten może być wykorzystany przez Twoich znajomych do znalezienia Twojego profilu.";
"auth_add_email_message_2" = "Ustaw adres e-mail na wypadek konieczności przywracania dostępu do konta, dodatkowo adres ten może być wykorzystany przez Twoich znajomych do znalezienia Twojego profilu.";
+"event_formatter_group_call_incoming" = "%@ w %@";
+"event_formatter_group_call_leave" = "Opuść";
+"event_formatter_group_call_join" = "Dołącz";
+"event_formatter_group_call" = "Połączenie grupowe";
+"event_formatter_call_end_call" = "Zakończ połączenie";
+"event_formatter_call_retry" = "Ponów";
+"event_formatter_call_decline" = "Odrzuć";
+"event_formatter_call_answer" = "Odbierz";
+"event_formatter_call_connection_failed" = "Połączenie nieudane";
+"event_formatter_call_you_missed" = "Przegapiłeś(-aś) to połączenie";
+"event_formatter_call_ringing" = "Dzwonie…";
+"event_formatter_call_connecting" = "Łączenie…";
+"settings_labs_enable_ringing_for_group_calls" = "Dźwięk połączeń grupowych";
+"room_no_privileges_to_create_group_call" = "Aby rozpocząć połączenie, musisz być administratorem lub moderatorem.";
+"room_join_group_call" = "Dołącz";
+
+// Chat
+"room_slide_to_end_group_call" = "Przesuń, aby zakończyć połączenie dla wszystkich";
+"callbar_only_single_active_group" = "Dotknij, aby powrócić do rozmowy grupowej (%@)";
diff --git a/Riot/Assets/sq.lproj/Localizable.strings b/Riot/Assets/sq.lproj/Localizable.strings
index b7feaff09..01714f3a2 100644
--- a/Riot/Assets/sq.lproj/Localizable.strings
+++ b/Riot/Assets/sq.lproj/Localizable.strings
@@ -62,3 +62,9 @@
/* New message indicator on a room */
"MESSAGE_IN_X" = "Mesazh te %@";
"MESSAGE_PROTECTED" = "Mesazh i Ri";
+
+/* Group call from user, CallKit caller name */
+"GROUP_CALL_FROM_USER" = "%@ (Thirrje në grup)";
+
+/* A user added a Jitsi call to a room */
+"GROUP_CALL_STARTED" = "Nisi thirrje në grup";
diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings
index ebf05c4a3..5277cec95 100644
--- a/Riot/Assets/sq.lproj/Vector.strings
+++ b/Riot/Assets/sq.lproj/Vector.strings
@@ -162,7 +162,7 @@
"room_participants_action_start_video_call" = "Nis thirrje video";
"room_participants_action_mention" = "Përmendje";
// Chat
-"room_jump_to_first_unread" = "Hidhu te mesazhi i parë i palexuar";
+"room_jump_to_first_unread" = "Hidhu te të palexuar";
"room_new_message_notification" = "%d mesazh i ri";
"room_new_messages_notification" = "%d mesazhe të rinj";
"room_one_user_is_typing" = "%@ po shtyp…";
@@ -170,7 +170,7 @@
"room_many_users_are_typing" = "%@, %@ & të tjerë po shtypin…";
"room_message_placeholder" = "Dërgoni një mesazh (të pafshehtëzuar)…";
"room_do_not_have_permission_to_post" = "S’keni leje të postoni në këtë dhomë";
-"encrypted_room_message_placeholder" = "Dërgoni një mesazhi të fshehtëzuar…";
+"encrypted_room_message_placeholder" = "Dërgoni një mesazh të fshehtëzuar…";
"room_message_short_placeholder" = "Dërgoni një mesazh…";
"room_offline_notification" = "Humbi lidhja me shërbyesin.";
"room_unsent_messages_notification" = "Mesazhet s’u dërguan.";
@@ -1309,8 +1309,8 @@
"call_actions_unhold" = "Rimerre";
"event_formatter_call_back" = "Ktheji thirrjen";
"event_formatter_call_you_declined" = "Hodhët poshtë këtë thirrje";
-"event_formatter_call_you_currently_in" = "Gjendeni në këtë thirrje";
-"event_formatter_call_has_ended" = "Kjo thirrje ka përfunduar";
+"event_formatter_call_you_currently_in" = "Thirrje aktive";
+"event_formatter_call_has_ended" = "Përfundoi %@";
"event_formatter_call_video" = "Thirrje video";
"event_formatter_call_voice" = "Thirrje audio";
"security_settings_crosssigning_reset" = "Rikthe te parazgjedhjet cross-signing";
@@ -1338,10 +1338,29 @@
"callbar_active_and_single_paused" = "1 thirrje aktive (%@) · 1 thirrje e ndalur";
// Call Bar
-"callbar_only_single_active" = "Thirrje aktive (%@)";
+"callbar_only_single_active" = "Prekeni që të riktheheni te thirrja (%@)";
"room_details_integrations" = "Integrime";
"room_multiple_typing_notification" = "%@ dhe të tjerë";
"room_accessibility_video_call" = "Thirrje Video";
"room_message_replying_to" = "Në përgjigje të %@";
"room_message_editing" = "Përpunim";
"room_details_search" = "Kërkoni për dhomë";
+"settings_labs_enable_ringing_for_group_calls" = "Bjeri ziles për thirrje në grup";
+"event_formatter_group_call_incoming" = "%@ në %@";
+"event_formatter_group_call_leave" = "Dilni";
+"event_formatter_group_call_join" = "Hyni";
+"event_formatter_group_call" = "Thirrje në grup";
+"event_formatter_call_end_call" = "Përfundoje thirrjen";
+"event_formatter_call_retry" = "Riprovo";
+"event_formatter_call_answer" = "Përgjigjuni";
+"event_formatter_call_decline" = "Hidhe poshtë";
+"event_formatter_call_connection_failed" = "Lidhja dështoi";
+"event_formatter_call_you_missed" = "E humbët këtë thirrje";
+"event_formatter_call_ringing" = "Po i bihet ziles…";
+"event_formatter_call_connecting" = "Po lidhet…";
+"room_no_privileges_to_create_group_call" = "Që të nisni një thirrje, duhet të jeni një përgjegjës ose një moderator.";
+"room_join_group_call" = "Hyni";
+
+// Chat
+"room_slide_to_end_group_call" = "Rrëshqiteni, që të përfundohet thirrja për këdo";
+"callbar_only_single_active_group" = "Prekeni, që të Hyni në thirrjen e grupit (%@)";
diff --git a/Riot/Assets/sv.lproj/Localizable.strings b/Riot/Assets/sv.lproj/Localizable.strings
index 610be3c72..6a6fb903e 100644
--- a/Riot/Assets/sv.lproj/Localizable.strings
+++ b/Riot/Assets/sv.lproj/Localizable.strings
@@ -62,3 +62,9 @@
"VIDEO_CONF_NAMED_FROM_USER" = "Videogruppsamtal från %@: '%@'";
"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ vill verifiera";
"MESSAGE_PROTECTED" = "Nytt meddelande";
+
+/* Group call from user, CallKit caller name */
+"GROUP_CALL_FROM_USER" = "%@ (Gruppsamtal)";
+
+/* A user added a Jitsi call to a room */
+"GROUP_CALL_STARTED" = "Gruppsamtal startat";
diff --git a/Riot/Assets/sv.lproj/Vector.strings b/Riot/Assets/sv.lproj/Vector.strings
index c90ad4d0c..0830ceac4 100644
--- a/Riot/Assets/sv.lproj/Vector.strings
+++ b/Riot/Assets/sv.lproj/Vector.strings
@@ -185,7 +185,7 @@
"room_member_power_level_short_moderator" = "Mod";
"room_member_power_level_short_custom" = "Anpassad";
// Chat
-"room_jump_to_first_unread" = "Hoppa till första olästa meddelandet";
+"room_jump_to_first_unread" = "Hoppa till oläst";
"room_new_message_notification" = "%d nytt meddelande";
"room_new_messages_notification" = "%u nya meddelanden";
"room_one_user_is_typing" = "%@ skriver…";
@@ -1267,8 +1267,8 @@
"call_actions_unhold" = "Återuppta";
"event_formatter_call_back" = "Ring tillbaka";
"event_formatter_call_you_declined" = "Du avslog det här samtalet";
-"event_formatter_call_you_currently_in" = "Du är för närvarande i det här samtalet";
-"event_formatter_call_has_ended" = "Det här samtalet har avslutats";
+"event_formatter_call_you_currently_in" = "Aktivt samtal";
+"event_formatter_call_has_ended" = "Avslutade %@";
"event_formatter_call_video" = "Videosamtal";
"event_formatter_call_voice" = "Röstsamtal";
"settings_show_NSFW_public_rooms" = "Visa NSFW offentliga rum";
@@ -1285,10 +1285,29 @@
"callbar_active_and_single_paused" = "1 aktivt samtal (%@) · 1 pausat samtal";
// Call Bar
-"callbar_only_single_active" = "Aktivt samtal (%@)";
+"callbar_only_single_active" = "Tryck för att återgå till samtalet (%@)";
"room_accessibility_video_call" = "Videosamtal";
"room_message_editing" = "Redigerar";
"room_message_replying_to" = "Svarar på %@";
"room_multiple_typing_notification" = "%@ och andra";
"room_details_search" = "Sök i rum";
"room_details_integrations" = "Integrationer";
+"event_formatter_group_call_incoming" = "%@ i %@";
+"event_formatter_group_call_leave" = "Lämna";
+"event_formatter_group_call_join" = "Gå med";
+"event_formatter_group_call" = "Gruppsamtal";
+"event_formatter_call_end_call" = "Avsluta samtal";
+"event_formatter_call_retry" = "Försök igen";
+"event_formatter_call_answer" = "Svara";
+"event_formatter_call_decline" = "Neka";
+"event_formatter_call_connection_failed" = "Anslutning misslyckades";
+"event_formatter_call_you_missed" = "Du missade det här samtalet";
+"event_formatter_call_ringing" = "Ringer…";
+"event_formatter_call_connecting" = "Ansluter…";
+"settings_labs_enable_ringing_for_group_calls" = "Ring för gruppsamtal";
+"room_no_privileges_to_create_group_call" = "Du måste vara en administratör eller moderator för att starta ett samtal.";
+"room_join_group_call" = "Gå med";
+
+// Chat
+"room_slide_to_end_group_call" = "Glid för att avsluta samtalet för alla";
+"callbar_only_single_active_group" = "Tryck för att gå med i gruppsamtalet (%@)";
diff --git a/Riot/Assets/uk.lproj/Localizable.strings b/Riot/Assets/uk.lproj/Localizable.strings
index 3d3b665e2..ae91fabd8 100644
--- a/Riot/Assets/uk.lproj/Localizable.strings
+++ b/Riot/Assets/uk.lproj/Localizable.strings
@@ -62,3 +62,9 @@
/* New message indicator on a room */
"MESSAGE_IN_X" = "Повідомлення у %@";
"MESSAGE_PROTECTED" = "Нове повідомлення";
+
+/* Group call from user, CallKit caller name */
+"GROUP_CALL_FROM_USER" = "%@ (Груповий виклик)";
+
+/* A user added a Jitsi call to a room */
+"GROUP_CALL_STARTED" = "Груповий виклик розпочато";
diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings
index a2942949a..8fa7d8a86 100644
--- a/Riot/Assets/uk.lproj/Vector.strings
+++ b/Riot/Assets/uk.lproj/Vector.strings
@@ -296,7 +296,7 @@
"callbar_active_and_single_paused" = "1 активний виклик (%@) · 1 виклик в очікуванні";
// Call Bar
-"callbar_only_single_active" = "Активний виклик (%@)";
+"callbar_only_single_active" = "Торкніться, щоб повернутися до виклику (%@)";
"switch" = "Перемкнути";
"room_event_action_resend" = "Повторно надіслати";
"room_event_action_redact" = "Вилучити";
diff --git a/Riot/Categories/UITableViewCell.swift b/Riot/Categories/UITableViewCell.swift
index e9ea1f8ba..dfd80a9c2 100644
--- a/Riot/Categories/UITableViewCell.swift
+++ b/Riot/Categories/UITableViewCell.swift
@@ -33,6 +33,11 @@ extension UITableViewCell {
return result
}
+ // Hide separator for one cell, otherwise use `tableView.separatorStyle = .none`
+ @objc func vc_hideSeparator() {
+ self.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: .greatestFiniteMagnitude)
+ }
+
@objc func vc_setAccessoryDisclosureIndicator(withTintColor tintColor: UIColor) {
let disclosureImage = Asset.Images.disclosureIcon.image.withRenderingMode(.alwaysTemplate)
let disclosureImageView = UIImageView(image: disclosureImage)
diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift
index 4565353fe..08f3dbdf8 100644
--- a/Riot/Generated/Images.swift
+++ b/Riot/Generated/Images.swift
@@ -158,6 +158,8 @@ internal enum Asset {
internal static let secretsResetWarning = ImageAsset(name: "secrets_reset_warning")
internal static let removeIconPink = ImageAsset(name: "remove_icon_pink")
internal static let settingsIcon = ImageAsset(name: "settings_icon")
+ internal static let featureUnavaibleArtwork = ImageAsset(name: "feature_unavaible_artwork")
+ internal static let featureUnavaibleArtworkDark = ImageAsset(name: "feature_unavaible_artwork_dark")
internal static let tabFavourites = ImageAsset(name: "tab_favourites")
internal static let tabGroups = ImageAsset(name: "tab_groups")
internal static let tabHome = ImageAsset(name: "tab_home")
diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift
index 0b1492b69..8e7290c1a 100644
--- a/Riot/Generated/Storyboards.swift
+++ b/Riot/Generated/Storyboards.swift
@@ -229,6 +229,11 @@ internal enum StoryboardScene {
internal static let initialScene = InitialSceneType(storyboard: SimpleScreenTemplateViewController.self)
}
+ internal enum SpaceFeatureUnaivableViewController: StoryboardType {
+ internal static let storyboardName = "SpaceFeatureUnaivableViewController"
+
+ internal static let initialScene = InitialSceneType(storyboard: SpaceFeatureUnaivableViewController.self)
+ }
internal enum TemplateScreenViewController: StoryboardType {
internal static let storyboardName = "TemplateScreenViewController"
diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift
index 11d0dc52d..8a7a8bae0 100644
--- a/Riot/Generated/Strings.swift
+++ b/Riot/Generated/Strings.swift
@@ -4562,6 +4562,34 @@ internal enum VectorL10n {
internal static var socialLoginListTitleSignUp: String {
return VectorL10n.tr("Vector", "social_login_list_title_sign_up")
}
+ /// BETA
+ internal static var spaceBetaAnnounceBadge: String {
+ return VectorL10n.tr("Vector", "space_beta_announce_badge")
+ }
+ /// Spaces are a new way to group rooms and people. They’re not on iOS yet, but you can use them now on Web and Desktop.
+ internal static var spaceBetaAnnounceInformation: String {
+ return VectorL10n.tr("Vector", "space_beta_announce_information")
+ }
+ /// The new version of communities
+ internal static var spaceBetaAnnounceSubtitle: String {
+ return VectorL10n.tr("Vector", "space_beta_announce_subtitle")
+ }
+ /// Spaces are coming soon
+ internal static var spaceBetaAnnounceTitle: String {
+ return VectorL10n.tr("Vector", "space_beta_announce_title")
+ }
+ /// Spaces are a new way to group rooms and people.\n\nThey’ll be here soon. For now, if you join one on another platform, you will be able to access any rooms you join here.
+ internal static var spaceFeatureUnavailableInformation: String {
+ return VectorL10n.tr("Vector", "space_feature_unavailable_information")
+ }
+ /// Spaces aren't on iOS yet, but you can use them now on Web and Desktop
+ internal static var spaceFeatureUnavailableSubtitle: String {
+ return VectorL10n.tr("Vector", "space_feature_unavailable_subtitle")
+ }
+ /// Spaces aren’t here yet
+ internal static var spaceFeatureUnavailableTitle: String {
+ return VectorL10n.tr("Vector", "space_feature_unavailable_title")
+ }
/// Start
internal static var start: String {
return VectorL10n.tr("Vector", "start")
diff --git a/Riot/Managers/KeyValueStorage/KeyValueStore.swift b/Riot/Managers/KeyValueStorage/KeyValueStore.swift
index e0a292dfe..e70bba8ea 100644
--- a/Riot/Managers/KeyValueStorage/KeyValueStore.swift
+++ b/Riot/Managers/KeyValueStorage/KeyValueStore.swift
@@ -38,4 +38,5 @@ protocol KeyValueStore {
// remove
func removeObject(forKey key: KeyValueStoreKey) throws
+ func removeAll() throws
}
diff --git a/Riot/Managers/KeyValueStorage/KeychainStore.swift b/Riot/Managers/KeyValueStorage/KeychainStore.swift
index 4ebdf412f..8f694cb54 100644
--- a/Riot/Managers/KeyValueStorage/KeychainStore.swift
+++ b/Riot/Managers/KeyValueStorage/KeychainStore.swift
@@ -114,4 +114,8 @@ extension KeychainStore: KeyValueStore {
try keychain.remove(key)
}
+ func removeAll() throws {
+ try keychain.removeAll()
+ }
+
}
diff --git a/Riot/Managers/KeyValueStorage/MemoryStore.swift b/Riot/Managers/KeyValueStorage/MemoryStore.swift
index 639f3bcd6..993f0f4c7 100644
--- a/Riot/Managers/KeyValueStorage/MemoryStore.swift
+++ b/Riot/Managers/KeyValueStorage/MemoryStore.swift
@@ -92,4 +92,8 @@ extension MemoryStore: KeyValueStore {
map.removeValue(forKey: key)
}
+ func removeAll() throws {
+ map.removeAll()
+ }
+
}
diff --git a/Riot/Managers/PushNotification/PushNotificationService.m b/Riot/Managers/PushNotification/PushNotificationService.m
index be6a61c79..530fc53ce 100644
--- a/Riot/Managers/PushNotification/PushNotificationService.m
+++ b/Riot/Managers/PushNotification/PushNotificationService.m
@@ -299,25 +299,15 @@ Matrix session observer used to detect new opened sessions.
NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts;
for (MXKAccount *account in mxAccounts)
{
- // Check the current session state
- if (account.mxSession.state == MXSessionStatePaused)
- {
- NSLog(@"[PushNotificationService] launchBackgroundSync");
- MXWeakify(self);
+ NSLog(@"[PushNotificationService] launchBackgroundSync");
- [account backgroundSync:20000 success:^{
-
- // Sanity check
- MXStrongifyAndReturnIfNil(self);
-
- [[UNUserNotificationCenter currentNotificationCenter] removeUnwantedNotifications];
- [[UNUserNotificationCenter currentNotificationCenter] removeCallNotificationsFor:nil];
- NSLog(@"[PushNotificationService] launchBackgroundSync: the background sync succeeds");
- } failure:^(NSError *error) {
-
- NSLog(@"[PushNotificationService] launchBackgroundSync: the background sync failed. Error: %@ (%@).", error.domain, @(error.code));
- }];
- }
+ [account backgroundSync:20000 success:^{
+ [[UNUserNotificationCenter currentNotificationCenter] removeUnwantedNotifications];
+ [[UNUserNotificationCenter currentNotificationCenter] removeCallNotificationsFor:nil];
+ NSLog(@"[PushNotificationService] launchBackgroundSync: the background sync succeeds");
+ } failure:^(NSError *error) {
+ NSLog(@"[PushNotificationService] launchBackgroundSync: the background sync failed. Error: %@ (%@).", error.domain, @(error.code));
+ }];
}
}
@@ -564,16 +554,16 @@ Matrix session observer used to detect new opened sessions.
if (@available(iOS 13.0, *))
{
// for iOS 13, we'll just report the incoming call in the same runloop. It means we cannot call an async API here.
- MXEvent *lastCallInvite = _pushNotificationStore.lastCallInvite;
+ MXEvent *callInvite = [_pushNotificationStore callInviteForEventId:eventId];
// remove event
- _pushNotificationStore.lastCallInvite = nil;
+ [_pushNotificationStore removeCallInviteWithEventId:eventId];
MXSession *session = [AppDelegate theDelegate].mxSessions.firstObject;
// when we have a VoIP push while the application is killed, session.callManager will not be ready yet. Configure it.
[[AppDelegate theDelegate] configureCallManagerIfRequiredForSession:session];
- NSLog(@"[PushNotificationService] didReceiveIncomingPushWithPayload: lastCallInvite: %@", lastCallInvite);
+ NSLog(@"[PushNotificationService] didReceiveIncomingPushWithPayload: callInvite: %@", callInvite);
- if ([lastCallInvite.eventId isEqualToString:eventId])
+ if (callInvite)
{
// We're using this dispatch_group to continue event stream after cache fully processed.
dispatch_group_t dispatchGroup = dispatch_group_create();
@@ -585,18 +575,12 @@ Matrix session observer used to detect new opened sessions.
dispatch_group_leave(dispatchGroup);
}];
- if (lastCallInvite.isEncrypted && ![session decryptEvent:lastCallInvite inTimeline:nil])
- {
- NSLog(@"[PushNotificationService] didReceiveIncomingPushWithPayload: Failed to decrypt the call invite event: %@", eventId);
- completion();
- return;
- }
-
- if (lastCallInvite.eventType == MXEventTypeCallInvite)
+ if (callInvite.eventType == MXEventTypeCallInvite)
{
// process the call invite synchronously
- [session.callManager handleCallEvent:lastCallInvite];
- MXCall *call = [session.callManager callWithCallId:lastCallInvite.content[@"call_id"]];
+ [session.callManager handleCallEvent:callInvite];
+ MXCallInviteEventContent *content = [MXCallInviteEventContent modelFromJSON:callInvite.content];
+ MXCall *call = [session.callManager callWithCallId:content.callId];
if (call)
{
[session.callManager.callKitAdapter reportIncomingCall:call];
@@ -613,10 +597,10 @@ Matrix session observer used to detect new opened sessions.
NSLog(@"[PushNotificationService] didReceiveIncomingPushWithPayload: Error on call object on room %@ for the event: %@", roomId, eventId);
}
}
- else if ([lastCallInvite.type isEqualToString:kWidgetMatrixEventTypeString] ||
- [lastCallInvite.type isEqualToString:kWidgetModularEventTypeString])
+ else if ([callInvite.type isEqualToString:kWidgetMatrixEventTypeString] ||
+ [callInvite.type isEqualToString:kWidgetModularEventTypeString])
{
- [[AppDelegate theDelegate].callPresenter processWidgetEvent:lastCallInvite
+ [[AppDelegate theDelegate].callPresenter processWidgetEvent:callInvite
inSession:session];
}
else
@@ -628,7 +612,7 @@ Matrix session observer used to detect new opened sessions.
else
{
// It's a serious error. There is nothing to avoid iOS to kill us here.
- NSLog(@"[PushNotificationService] didReceiveIncomingPushWithPayload: iOS 13 and in bg, but we don't have the last callInvite event for the event %@. There is something wrong.", eventId);
+ NSLog(@"[PushNotificationService] didReceiveIncomingPushWithPayload: iOS 13 and in bg, but we don't have the callInvite event for the eventId: %@. There is something wrong.", eventId);
}
}
else
diff --git a/Riot/Managers/PushNotification/PushNotificationStore.swift b/Riot/Managers/PushNotification/PushNotificationStore.swift
index 694df3309..ea2ba1852 100644
--- a/Riot/Managers/PushNotification/PushNotificationStore.swift
+++ b/Riot/Managers/PushNotification/PushNotificationStore.swift
@@ -29,7 +29,6 @@ final class PushNotificationStore: NSObject {
private struct StoreKeys {
static let pushToken: String = "pushtoken"
- static let lastCallInvite: String = "lastCallInvite"
}
/// Store. Defaults to `KeychainStore`
@@ -59,32 +58,23 @@ final class PushNotificationStore: NSObject {
}
}
- var lastCallInvite: MXEvent? {
- get {
- do {
- guard let data = try store.data(forKey: StoreKeys.lastCallInvite) else {
- return nil
- }
- return NSKeyedUnarchiver.unarchiveObject(with: data) as? MXEvent
- } catch let error {
- NSLog("[PinCodePreferences] Error when reading push token from store: \(error)")
- return nil
- }
- } set {
- do {
- guard let newValue = newValue else {
- return try store.removeObject(forKey: StoreKeys.lastCallInvite)
- }
- let data = NSKeyedArchiver.archivedData(withRootObject: newValue)
- try store.set(data, forKey: StoreKeys.lastCallInvite)
- } catch let error {
- NSLog("[PinCodePreferences] Error when storing push token to the store: \(error)")
- }
+ func callInvite(forEventId eventId: String) -> MXEvent? {
+ guard let data = try? store.data(forKey: eventId) else {
+ return nil
}
+ return NSKeyedUnarchiver.unarchiveObject(with: data) as? MXEvent
+ }
+
+ func storeCallInvite(_ event: MXEvent) {
+ let data = NSKeyedArchiver.archivedData(withRootObject: event)
+ try? store.set(data, forKey: event.eventId)
+ }
+
+ func removeCallInvite(withEventId eventId: String) {
+ try? store.removeObject(forKey: eventId)
}
func reset() {
- pushKitToken = nil
- lastCallInvite = nil
+ try? store.removeAll()
}
}
diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift
index 954741ccb..533702c2b 100644
--- a/Riot/Managers/Settings/RiotSettings.swift
+++ b/Riot/Managers/Settings/RiotSettings.swift
@@ -79,6 +79,7 @@ final class RiotSettings: NSObject {
static let roomInfoScreenShowIntegrations = "roomInfoScreenShowIntegrations"
static let roomMemberScreenShowIgnore = "roomMemberScreenShowIgnore"
static let unifiedSearchScreenShowPublicDirectory = "unifiedSearchScreenShowPublicDirectory"
+ static let hideSpaceBetaAnnounce = "hideSpaceBetaAnnounce"
}
static let shared = RiotSettings()
@@ -707,4 +708,13 @@ final class RiotSettings: NSObject {
}
}
+ // MARK: - Beta
+
+ var hideSpaceBetaAnnounce: Bool {
+ get {
+ return defaults.bool(forKey: UserDefaultsKeys.hideSpaceBetaAnnounce)
+ } set {
+ defaults.set(newValue, forKey: UserDefaultsKeys.hideSpaceBetaAnnounce)
+ }
+ }
}
diff --git a/Riot/Modules/Application/LegacyAppDelegate.h b/Riot/Modules/Application/LegacyAppDelegate.h
index 1cea08e0d..6935122c2 100644
--- a/Riot/Modules/Application/LegacyAppDelegate.h
+++ b/Riot/Modules/Application/LegacyAppDelegate.h
@@ -224,6 +224,38 @@ UINavigationControllerDelegate
*/
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment;
+/**
+ Process the fragment part of a vector.im link.
+
+ @param fragment the fragment part of the universal link.
+ @param universalLinkURL the unprocessed the universal link URL (optional).
+ @return YES in case of processing success.
+ */
+- (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromURL:(NSURL*)universalLinkURL;
+
+/**
+ Process the URL of a vector.im link.
+
+ @param universalLinkURL the universal link URL.
+ @return YES in case of processing success.
+ */
+- (BOOL)handleUniversalLinkURL:(NSURL*)universalLinkURL;
+
+#pragma mark - Jitsi call
+
+/**
+ Open the Jitsi view controller from a widget.
+
+ @param jitsiWidget the jitsi widget.
+ @param video to indicate voice or video call.
+ */
+- (void)displayJitsiViewControllerWithWidget:(Widget*)jitsiWidget andVideo:(BOOL)video;
+
+/**
+ The current Jitsi view controller being displayed.
+ */
+@property (nonatomic, readonly) JitsiViewController *jitsiViewController;
+
#pragma mark - Call status handling
/**
diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m
index ece6aad63..96fa6fa10 100644
--- a/Riot/Modules/Application/LegacyAppDelegate.m
+++ b/Riot/Modules/Application/LegacyAppDelegate.m
@@ -228,6 +228,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
@property (nonatomic, strong) MajorUpdateManager *majorUpdateManager;
+@property (nonatomic, strong) SpaceFeatureUnavailablePresenter *spaceFeatureUnavailablePresenter;
+
@end
@implementation LegacyAppDelegate
@@ -389,6 +391,12 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
NSLog(@"[AppDelegate] didFinishLaunchingWithOptions: isProtectedDataAvailable: %@", @([application isProtectedDataAvailable]));
+ if (![application isProtectedDataAvailable])
+ {
+ NSLog(@"[AppDelegate] didFinishLaunchingWithOptions: Terminating the app because protected data not available");
+ exit(0);
+ }
+
_configuration = [AppConfiguration new];
// Log app information
@@ -461,6 +469,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
self.pushNotificationStore = [PushNotificationStore new];
self.pushNotificationService = [[PushNotificationService alloc] initWithPushNotificationStore:self.pushNotificationStore];
self.pushNotificationService.delegate = self;
+
+ self.spaceFeatureUnavailablePresenter = [SpaceFeatureUnavailablePresenter new];
// Add matrix observers, and initialize matrix sessions if the app is not launched in background.
[self initMatrixSessions];
@@ -1212,7 +1222,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// Continue the registration with the passed nextLink
NSLog(@"[AppDelegate] handleUniversalLink. Complete registration with nextLink");
NSURL *nextLink = [NSURL URLWithString:queryParams[@"nextLink"]];
- [self handleUniversalLinkFragment:nextLink.fragment];
+ [self handleUniversalLinkFragment:nextLink.fragment fromURL:nextLink];
}
else
{
@@ -1238,10 +1248,15 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
return YES;
}
- return [self handleUniversalLinkFragment:webURL.fragment];
+ return [self handleUniversalLinkFragment:webURL.fragment fromURL:webURL];
}
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment
+{
+ return [self handleUniversalLinkFragment:fragment fromURL:nil];
+}
+
+- (BOOL)handleUniversalLinkFragment:(NSString*)fragment fromURL:(NSURL*)universalLinkURL
{
BOOL continueUserActivity = NO;
MXKAccountManager *accountManager = [MXKAccountManager sharedManager];
@@ -1325,19 +1340,32 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
if (account)
{
NSString *roomId = roomIdOrAlias;
+ MXRoom *room;
// Translate the alias into the room id
if ([roomIdOrAlias hasPrefix:@"#"])
{
- MXRoom *room = [account.mxSession roomWithAlias:roomIdOrAlias];
+ room = [account.mxSession roomWithAlias:roomIdOrAlias];
if (room)
{
roomId = room.roomId;
}
}
+ else
+ {
+ room = [account.mxSession roomWithRoomId:roomId];
+ }
- // Open the room page
- [self showRoom:roomId andEventId:eventId withMatrixSession:account.mxSession];
+ if (room.summary.roomType == MXRoomTypeSpace)
+ {
+ // Indicates that spaces are not supported
+ [self.spaceFeatureUnavailablePresenter presentUnavailableFeatureFrom:self.presentedViewController animated:YES];
+ }
+ else
+ {
+ // Open the room page
+ [self showRoom:roomId andEventId:eventId withMatrixSession:account.mxSession];
+ }
continueUserActivity = YES;
}
@@ -1387,7 +1415,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
{
universalLinkFragmentPendingRoomAlias = @{roomId: roomIdOrAlias};
- [self handleUniversalLinkFragment:newUniversalLinkFragment];
+ [self handleUniversalLinkFragment:newUniversalLinkFragment fromURL:universalLinkURL];
}
else
{
@@ -1425,7 +1453,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
if (notif.object == account.mxSession && account.mxSession.state == MXSessionStateRunning)
{
NSLog(@"[AppDelegate] Universal link: The session is running. Retry the link");
- [self handleUniversalLinkFragment:fragment];
+ [self handleUniversalLinkFragment:fragment fromURL:universalLinkURL];
}
}
}];
@@ -1486,7 +1514,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
if ([universalLinkFragmentPending isEqualToString:fragment])
{
NSLog(@"[AppDelegate] Universal link: The user is now logged in. Retry the link");
- [self handleUniversalLinkFragment:fragment];
+ [self handleUniversalLinkFragment:fragment fromURL:universalLinkURL];
}
}];
}
@@ -1551,7 +1579,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
if ([universalLinkFragmentPending isEqualToString:fragment])
{
NSLog(@"[AppDelegate] Universal link: The user is now logged in. Retry the link");
- [self handleUniversalLinkFragment:fragment];
+ [self handleUniversalLinkFragment:fragment fromURL:universalLinkURL];
}
}];
}
@@ -1575,6 +1603,14 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
return continueUserActivity;
}
+- (BOOL)handleUniversalLinkURL:(NSURL*)universalLinkURL
+{
+ // iOS Patch: fix vector.im urls before using it
+ NSURL *fixedURL = [Tools fixURLWithSeveralHashKeys:universalLinkURL];
+
+ return [self handleUniversalLinkFragment:fixedURL.fragment fromURL:universalLinkURL];
+}
+
- (void)resetPendingUniversalLink
{
universalLinkFragmentPending = nil;
@@ -2740,6 +2776,25 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
- (void)showRoom:(NSString*)roomId andEventId:(NSString*)eventId withMatrixSession:(MXSession*)mxSession restoreInitialDisplay:(BOOL)restoreInitialDisplay completion:(void (^)(void))completion
{
+ if (roomId && mxSession)
+ {
+ MXRoom *room = [mxSession roomWithRoomId:roomId];
+
+ // Indicates that spaces are not supported
+ if (room.summary.roomType == MXRoomTypeSpace)
+ {
+
+ [self.spaceFeatureUnavailablePresenter presentUnavailableFeatureFrom:self.presentedViewController animated:YES];
+
+ if (completion)
+ {
+ completion();
+ }
+
+ return;
+ }
+ }
+
void (^selectRoom)(void) = ^() {
// Select room to display its details (dispatch this action in order to let TabBarController end its refresh)
[self.masterTabBarController selectRoomWithId:roomId andEventId:eventId inMatrixSession:mxSession completion:^{
diff --git a/Riot/Modules/Common/Buttons/Close/CloseButton.swift b/Riot/Modules/Common/Buttons/Close/CloseButton.swift
index adf2b0226..22a0ba8c8 100644
--- a/Riot/Modules/Common/Buttons/Close/CloseButton.swift
+++ b/Riot/Modules/Common/Buttons/Close/CloseButton.swift
@@ -21,9 +21,9 @@ final class CloseButton: UIButton, Themable {
// MARK: - Constants
private enum CircleBackgroundConstants {
- static let height: CGFloat = 30.0
+ static let height: CGFloat = 26.0
static let highlightedAlha: CGFloat = 0.5
- static let normalAlha: CGFloat = 1.0
+ static let normalAlha: CGFloat = 0.8
}
// MARK: - Properties
@@ -109,6 +109,6 @@ final class CloseButton: UIButton, Themable {
func update(theme: Theme) {
self.theme = theme
- self.circleBackgroundView.backgroundColor = theme.headerTextSecondaryColor
+ self.circleBackgroundView.backgroundColor = theme.secondaryCircleButtonBackgroundColor
}
}
diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m
index 497f80fce..57474d9c8 100644
--- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m
+++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m
@@ -1188,7 +1188,11 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
}
else
{
- [conversationCellDataArray addObject:recentCellDataStoring];
+ // Hide spaces from home (keep space invites)
+ if (room.summary.roomType != MXRoomTypeSpace)
+ {
+ [conversationCellDataArray addObject:recentCellDataStoring];
+ }
}
}
else if (_recentsDataSourceMode == RecentsDataSourceModeFavourites)
@@ -1219,12 +1223,12 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
// Consider only non direct rooms.
if (!room.isDirect)
{
- // Keep only the invites, the favourites and the rooms without tag
+ // Keep only the invites, the favourites and the rooms without tag and room type different from space
if (room.summary.membership == MXMembershipInvite)
{
[invitesCellDataArray addObject:recentCellDataStoring];
}
- else if (!room.accountData.tags.count || room.accountData.tags[kMXRoomTagFavourite])
+ else if ((!room.accountData.tags.count || room.accountData.tags[kMXRoomTagFavourite]) && room.summary.roomType != MXRoomTypeSpace)
{
[conversationCellDataArray addObject:recentCellDataStoring];
}
diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m
index c396da4ff..e510c5227 100644
--- a/Riot/Modules/Common/Recents/RecentsViewController.m
+++ b/Riot/Modules/Common/Recents/RecentsViewController.m
@@ -70,6 +70,8 @@
@property (nonatomic, strong) RoomsDirectoryCoordinatorBridgePresenter *roomsDirectoryCoordinatorBridgePresenter;
+@property (nonatomic, strong) SpaceFeatureUnavailablePresenter *spaceFeatureUnavailablePresenter;
+
@end
@implementation RecentsViewController
@@ -870,6 +872,16 @@
return recentsDataSource;
}
+- (void)showSpaceInviteNotAvailable
+{
+ if (!self.spaceFeatureUnavailablePresenter)
+ {
+ self.spaceFeatureUnavailablePresenter = [SpaceFeatureUnavailablePresenter new];
+ }
+
+ [self.spaceFeatureUnavailablePresenter presentUnavailableFeatureFrom:self animated:YES];
+}
+
#pragma mark - MXKDataSourceDelegate
- (Class)cellViewClassForCellData:(MXKCellData*)cellData
@@ -905,6 +917,13 @@
{
// Retrieve the invited room
MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey];
+
+ if (invitedRoom.summary.roomType == MXRoomTypeSpace)
+ {
+ // Indicates that spaces are not supported
+ [self showSpaceInviteNotAvailable];
+ return;
+ }
// Display the room preview
[self dispayRoomWithRoomId:invitedRoom.roomId inMatrixSession:invitedRoom.mxSession];
@@ -913,6 +932,13 @@
{
// Retrieve the invited room
MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey];
+
+ if (invitedRoom.summary.roomType == MXRoomTypeSpace)
+ {
+ // Indicates that spaces are not supported
+ [self showSpaceInviteNotAvailable];
+ return;
+ }
// Accept invitation
[self joinRoom:invitedRoom completion:nil];
@@ -921,7 +947,7 @@
{
// Retrieve the invited room
MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey];
-
+
[self cancelEditionMode:isRefreshPending];
// Decline the invitation
@@ -1372,8 +1398,13 @@
// Retrieve the invited room
MXRoom* invitedRoom = cellData.roomSummary.room;
+ if (invitedRoom.summary.roomType == MXRoomTypeSpace)
+ {
+ // Indicates that spaces are not supported
+ [self showSpaceInviteNotAvailable];
+ }
// Check if can show preview for the invited room
- if ([self canShowRoomPreviewFor:invitedRoom])
+ else if ([self canShowRoomPreviewFor:invitedRoom])
{
// Display the room preview
[self dispayRoomWithRoomId:invitedRoom.roomId inMatrixSession:invitedRoom.mxSession];
diff --git a/Riot/Modules/Communities/DataSources/GroupsDataSource.m b/Riot/Modules/Communities/DataSources/GroupsDataSource.m
index 28a1cabe8..f0c84e190 100644
--- a/Riot/Modules/Communities/DataSources/GroupsDataSource.m
+++ b/Riot/Modules/Communities/DataSources/GroupsDataSource.m
@@ -15,9 +15,79 @@
*/
#import "GroupsDataSource.h"
+#import "Riot-Swift.h"
+
+@interface GroupsDataSource()
+
+@property (nonatomic) NSInteger betaAnnounceSection;
+@property (nonatomic) BOOL showBetaAnnounce;
+
+@end
@implementation GroupsDataSource
+- (instancetype)initWithMatrixSession:(MXSession *)matrixSession
+{
+ self = [super initWithMatrixSession:matrixSession];
+ if (self)
+ {
+ // TODO: Hide the banner for the moment. Wait for iterations on it.
+// _showBetaAnnounce = !RiotSettings.shared.hideSpaceBetaAnnounce;
+ _showBetaAnnounce = NO;
+ }
+ return self;
+}
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ NSInteger count = 0;
+ self.betaAnnounceSection = self.groupInvitesSection = self.joinedGroupsSection = -1;
+
+ // Check whether all data sources are ready before rendering groups.
+ if (self.state == MXKDataSourceStateReady)
+ {
+ if (self.showBetaAnnounce)
+ {
+ self.betaAnnounceSection = count++;
+ }
+ if (groupsInviteCellDataArray.count)
+ {
+ self.groupInvitesSection = count++;
+ }
+ if (groupsCellDataArray.count)
+ {
+ self.joinedGroupsSection = count++;
+ }
+ }
+
+ return count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ if (indexPath.section == self.betaAnnounceSection)
+ {
+ BetaAnnounceCell *cell = [tableView dequeueReusableCellWithIdentifier:BetaAnnounceCell.reuseIdentifier forIndexPath:indexPath];
+ [cell vc_hideSeparator];
+ [cell updateWithTheme:ThemeService.shared.theme];
+ cell.delegate = self;
+ return cell;
+
+ }
+
+ return [super tableView:tableView cellForRowAtIndexPath:indexPath];
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ if (section == self.betaAnnounceSection)
+ {
+ return 1;
+ }
+
+ return [super tableView:tableView numberOfRowsInSection:section];
+}
+
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSString* sectionTitle = nil;
@@ -44,4 +114,13 @@
return (indexPath.section == self.joinedGroupsSection);
}
+#pragma mark - BetaAnnounceCellDelegate
+
+- (void)betaAnnounceCellDidTapCloseButton:(BetaAnnounceCell *)cell
+{
+ self.showBetaAnnounce = NO;
+ RiotSettings.shared.hideSpaceBetaAnnounce = YES;
+ [self.delegate dataSource:self didCellChange:nil];
+}
+
@end
diff --git a/Riot/Modules/Communities/GroupsViewController.m b/Riot/Modules/Communities/GroupsViewController.m
index dadc807af..afeea1bd4 100644
--- a/Riot/Modules/Communities/GroupsViewController.m
+++ b/Riot/Modules/Communities/GroupsViewController.m
@@ -79,6 +79,7 @@
//Register here the customized cell view class used to render groups
[self.groupsTableView registerNib:GroupTableViewCell.nib forCellReuseIdentifier:GroupTableViewCell.defaultReuseIdentifier];
[self.groupsTableView registerNib:GroupInviteTableViewCell.nib forCellReuseIdentifier:GroupInviteTableViewCell.defaultReuseIdentifier];
+ [self.groupsTableView registerNib:BetaAnnounceCell.nib forCellReuseIdentifier:BetaAnnounceCell.reuseIdentifier];
// Hide line separators of empty cells
self.groupsTableView.tableFooterView = [[UIView alloc] init];
diff --git a/Riot/Modules/Communities/Home/GroupHomeViewController.m b/Riot/Modules/Communities/Home/GroupHomeViewController.m
index d0c0b2605..a9f939bf6 100644
--- a/Riot/Modules/Communities/Home/GroupHomeViewController.m
+++ b/Riot/Modules/Communities/Home/GroupHomeViewController.m
@@ -786,11 +786,8 @@
if ([Tools isUniversalLink:URL])
{
shouldInteractWithURL = NO;
-
- // iOS Patch: fix vector.im urls before using it
- NSURL *fixedURL = [Tools fixURLWithSeveralHashKeys:URL];
-
- [[AppDelegate theDelegate] handleUniversalLinkFragment:fixedURL.fragment];
+
+ [[AppDelegate theDelegate] handleUniversalLinkURL:URL];
}
// Open a detail screen about the clicked user
else if ([MXTools isMatrixUserIdentifier:absoluteURLString])
diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryBridgeCoordinatorPresenter.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryBridgeCoordinatorPresenter.swift
index 9804af4e8..258f9ef0b 100644
--- a/Riot/Modules/Room/ReactionHistory/ReactionHistoryBridgeCoordinatorPresenter.swift
+++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryBridgeCoordinatorPresenter.swift
@@ -60,6 +60,7 @@ final class ReactionHistoryCoordinatorBridgePresenter: NSObject {
let coordinatorPresentable = reactionHistoryCoordinator.toPresentable()
coordinatorPresentable.modalPresentationStyle = .formSheet
+ coordinatorPresentable.presentationController?.delegate = self
viewController.present(coordinatorPresentable, animated: animated, completion: nil)
reactionHistoryCoordinator.start()
@@ -87,3 +88,13 @@ extension ReactionHistoryCoordinatorBridgePresenter: ReactionHistoryCoordinatorD
self.delegate?.reactionHistoryCoordinatorBridgePresenterDelegateDidClose(self)
}
}
+
+// MARK: - UIAdaptivePresentationControllerDelegate
+
+extension ReactionHistoryCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate {
+
+ func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
+ self.delegate?.reactionHistoryCoordinatorBridgePresenterDelegateDidClose(self)
+ }
+
+}
diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m
index d08af5d10..d1f6babe1 100644
--- a/Riot/Modules/Room/RoomViewController.m
+++ b/Riot/Modules/Room/RoomViewController.m
@@ -3410,10 +3410,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
{
shouldDoAction = NO;
- // iOS Patch: fix vector.im urls before using it
- NSURL *fixedURL = [Tools fixURLWithSeveralHashKeys:url];
-
- [[AppDelegate theDelegate] handleUniversalLinkFragment:fixedURL.fragment];
+ [[AppDelegate theDelegate] handleUniversalLinkURL:url];
}
// Open a detail screen about the clicked user
else if ([MXTools isMatrixUserIdentifier:absoluteURLString])
@@ -3452,7 +3449,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
// Open the room or preview it
NSString *fragment = [NSString stringWithFormat:@"/room/%@", [MXTools encodeURIComponent:roomIdOrAlias]];
- [[AppDelegate theDelegate] handleUniversalLinkFragment:fragment];
+ [[AppDelegate theDelegate] handleUniversalLinkFragment:fragment fromURL:url];
}
// Preview the clicked group
else if ([MXTools isMatrixGroupIdentifier:absoluteURLString])
@@ -3461,7 +3458,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
// Open the group or preview it
NSString *fragment = [NSString stringWithFormat:@"/group/%@", [MXTools encodeURIComponent:absoluteURLString]];
- [[AppDelegate theDelegate] handleUniversalLinkFragment:fragment];
+ [[AppDelegate theDelegate] handleUniversalLinkFragment:fragment fromURL:url];
}
else if ([absoluteURLString hasPrefix:EventFormatterOnReRequestKeysLinkAction])
{
diff --git a/Riot/Modules/Spaces/BetaAnnounceCell.swift b/Riot/Modules/Spaces/BetaAnnounceCell.swift
new file mode 100644
index 000000000..dac6dca86
--- /dev/null
+++ b/Riot/Modules/Spaces/BetaAnnounceCell.swift
@@ -0,0 +1,94 @@
+//
+// 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 UIKit
+import Reusable
+
+@objc protocol BetaAnnounceCellDelegate: class {
+ func betaAnnounceCellDidTapCloseButton(_ cell: BetaAnnounceCell)
+}
+
+/// BetaAnnounceCell enables to show coming beta feature
+final class BetaAnnounceCell: UITableViewCell, Themable {
+
+ // MARK: - Constants
+
+ private enum Constants {
+ static let cardBackgroundViewCornersRadius: CGFloat = 8.0
+ }
+
+ // MARK: - Properties
+
+ @IBOutlet private weak var cardBackgroundView: UIView!
+ @IBOutlet private weak var closeButton: CloseButton!
+ @IBOutlet private weak var badgeView: UIView!
+ @IBOutlet private weak var badgeLabel: UILabel!
+ @IBOutlet private weak var titleLabel: UILabel!
+ @IBOutlet private weak var subtitleLabel: UILabel!
+ @IBOutlet private weak var informationLabel: UILabel!
+
+ @objc weak var delegate: BetaAnnounceCellDelegate?
+
+ // MARK: - Life cycle
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ // Initialization code
+
+ self.badgeLabel.text = VectorL10n.spaceBetaAnnounceBadge
+ self.titleLabel.text = VectorL10n.spaceBetaAnnounceTitle
+ self.subtitleLabel.text = VectorL10n.spaceBetaAnnounceSubtitle
+ self.informationLabel.text = VectorL10n.spaceBetaAnnounceInformation
+
+ self.badgeView.layer.masksToBounds = true
+ self.cardBackgroundView.layer.masksToBounds = true
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+
+ self.cardBackgroundView.layer.cornerRadius = Constants.cardBackgroundViewCornersRadius
+ self.badgeView.layer.cornerRadius = self.badgeView.frame.height/2
+ }
+
+ // MARK: - Public
+
+ func update(theme: Theme) {
+ self.closeButton.update(theme: theme)
+ self.titleLabel.textColor = theme.textPrimaryColor
+ self.subtitleLabel.textColor = theme.textPrimaryColor
+ self.informationLabel.textColor = theme.textSecondaryColor
+ self.cardBackgroundView.backgroundColor = theme.baseColor
+ self.contentView.backgroundColor = theme.backgroundColor
+ }
+
+ // MARK: - Actions
+
+ @IBAction private func closeButtonAction(_ sender: Any) {
+ self.delegate?.betaAnnounceCellDidTapCloseButton(self)
+ }
+}
+
+// Copy past from NibReusable in order to expose these methods to ObjC
+extension BetaAnnounceCell {
+ @objc static var reuseIdentifier: String {
+ return String(describing: self)
+ }
+
+ @objc static var nib: UINib {
+ return UINib(nibName: String(describing: self), bundle: Bundle(for: self))
+ }
+}
diff --git a/Riot/Modules/Spaces/BetaAnnounceCell.xib b/Riot/Modules/Spaces/BetaAnnounceCell.xib
new file mode 100644
index 000000000..1ecc88ccf
--- /dev/null
+++ b/Riot/Modules/Spaces/BetaAnnounceCell.xib
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.storyboard b/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.storyboard
new file mode 100644
index 000000000..129474cf7
--- /dev/null
+++ b/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.storyboard
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.swift b/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.swift
new file mode 100644
index 000000000..64dbda5eb
--- /dev/null
+++ b/Riot/Modules/Spaces/SpaceFeatureUnaivableViewController.swift
@@ -0,0 +1,97 @@
+//
+// 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 UIKit
+
+final class SpaceFeatureUnaivableViewController: UIViewController {
+
+ // MARK: - Properties
+
+ // MARK: Outlets
+
+ @IBOutlet private weak var artworkImageView: UIImageView!
+ @IBOutlet private weak var subtitleLabel: UILabel!
+ @IBOutlet private weak var informationLabel: UILabel!
+
+ // MARK: Private
+
+ private var theme: Theme!
+
+ // MARK: - Setup
+
+ class func instantiate() -> SpaceFeatureUnaivableViewController {
+ let viewController = StoryboardScene.SpaceFeatureUnaivableViewController.initialScene.instantiate()
+ viewController.theme = ThemeService.shared().theme
+ return viewController
+ }
+
+ // MARK: - Life cycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ self.setupViews()
+
+ self.registerThemeServiceDidChangeThemeNotification()
+ self.update(theme: self.theme)
+ }
+
+ override var preferredStatusBarStyle: UIStatusBarStyle {
+ return self.theme.statusBarStyle
+ }
+
+ // MARK: - Private
+
+ private func registerThemeServiceDidChangeThemeNotification() {
+ NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
+ }
+
+ @objc private func themeDidChange() {
+ self.update(theme: ThemeService.shared().theme)
+ }
+
+ private func setupViews() {
+ self.title = VectorL10n.spaceFeatureUnavailableTitle
+
+ self.subtitleLabel.text = VectorL10n.spaceFeatureUnavailableSubtitle
+ self.informationLabel.text = VectorL10n.spaceFeatureUnavailableInformation
+ }
+
+ // MARK: - Public
+
+ func update(theme: Theme) {
+ self.theme = theme
+
+ self.view.backgroundColor = theme.backgroundColor
+
+ if let navigationBar = self.navigationController?.navigationBar {
+ theme.applyStyle(onNavigationBar: navigationBar)
+ }
+
+ self.subtitleLabel.textColor = theme.textPrimaryColor
+ self.informationLabel.textColor = theme.textSecondaryColor
+
+ // Artwork image view
+
+ let artworkImage = ThemeService.shared().isCurrentThemeDark() ? Asset.Images.featureUnavaibleArtworkDark.image : Asset.Images.featureUnavaibleArtwork.image
+
+ self.artworkImageView.image = artworkImage
+ }
+
+ func fill(informationText: String, shareLink: URL) {
+ self.subtitleLabel.text = informationText
+ }
+}
diff --git a/Riot/Modules/Spaces/SpaceFeatureUnavailablePresenter.swift b/Riot/Modules/Spaces/SpaceFeatureUnavailablePresenter.swift
new file mode 100644
index 000000000..c7633f06a
--- /dev/null
+++ b/Riot/Modules/Spaces/SpaceFeatureUnavailablePresenter.swift
@@ -0,0 +1,47 @@
+//
+// 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 UIKit
+
+/// SpaceFeatureUnavailablePresenter enables to present modals for unavailable space features
+@objcMembers
+final class SpaceFeatureUnavailablePresenter: NSObject {
+
+ // MARK: - Constants
+
+ // MARK: - Properties
+
+ // MARK: Private
+
+ private weak var presentingViewController: UIViewController?
+
+ // MARK: - Public
+
+ func presentUnavailableFeature(from presentingViewController: UIViewController,
+ animated: Bool) {
+
+ let spaceFeatureUnavailableVC = SpaceFeatureUnaivableViewController.instantiate()
+
+ let navigationVC = RiotNavigationController(rootViewController: spaceFeatureUnavailableVC)
+
+ spaceFeatureUnavailableVC.navigationItem.rightBarButtonItem = MXKBarButtonItem(title: Bundle.mxk_localizedString(forKey: "ok"), style: .plain, action: { [weak navigationVC] in
+ navigationVC?.dismiss(animated: true)
+ })
+
+ navigationVC.modalPresentationStyle = .formSheet
+ presentingViewController.present(navigationVC, animated: animated, completion: nil)
+ }
+}
diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m
index f0038508c..11603bf59 100644
--- a/Riot/Utils/EventFormatter.m
+++ b/Riot/Utils/EventFormatter.m
@@ -365,6 +365,11 @@ static NSString *const kEventFormatterTimeFormat = @"HH:mm";
self.encryptedMessagesTextFont = [UIFont italicSystemFontOfSize:15];
self.emojiOnlyTextFont = [UIFont systemFontOfSize:48];
self.editionMentionTextFont = [UIFont systemFontOfSize:12];
+
+ // Handle space room type, enables to show space in room list
+ defaultRoomSummaryUpdater.showRoomTypeStrings = @[
+ MXRoomTypeStringSpace
+ ];
}
return self;
}
diff --git a/RiotNSE/NotificationService.swift b/RiotNSE/NotificationService.swift
index ec2230cb9..82fcda16e 100644
--- a/RiotNSE/NotificationService.swift
+++ b/RiotNSE/NotificationService.swift
@@ -18,10 +18,23 @@ import UserNotifications
import MatrixKit
import MatrixSDK
+/// The number of milliseconds in one second.
+private let MSEC_PER_SEC: TimeInterval = 1000
+
class NotificationService: UNNotificationServiceExtension {
+ private struct NSE {
+ enum Constants {
+ static let voipPushRequestTimeout: TimeInterval = 15
+ static let timeNeededToSendVoIPPushes: TimeInterval = 20
+ }
+ }
+
// MARK: - Properties
+ /// Receiving dates for notifications. Keys are eventId's
+ private var receiveDates: [String: Date] = [:]
+
/// Content handlers. Keys are eventId's
private var contentHandlers: [String: ((UNNotificationContent) -> Void)] = [:]
@@ -88,6 +101,9 @@ class NotificationService: UNNotificationServiceExtension {
return
}
+ // store receive date
+ receiveDates[eventId] = Date()
+
// read badge from "unread_count"
// no need to check before, if it's nil, the badge will remain unchanged
content.badge = userInfo["unread_count"] as? NSNumber
@@ -190,6 +206,10 @@ class NotificationService: UNNotificationServiceExtension {
}
private func processEvent(_ event: MXEvent) {
+ if let receiveDate = receiveDates[event.eventId] {
+ NSLog("[NotificationService] processEvent: notification receive delay: \(receiveDate.timeIntervalSince1970*MSEC_PER_SEC - TimeInterval(event.originServerTs)) ms")
+ }
+
guard let content = bestAttemptContents[event.eventId], let userAccount = userAccount else {
self.fallbackToBestAttemptContent(forEventId: event.eventId)
return
@@ -246,6 +266,7 @@ class NotificationService: UNNotificationServiceExtension {
// clear maps
contentHandlers.removeValue(forKey: eventId)
bestAttemptContents.removeValue(forKey: eventId)
+ receiveDates.removeValue(forKey: eventId)
// We are done for this push
NSLog("--------------------------------------------------------------------------------")
@@ -290,7 +311,14 @@ class NotificationService: UNNotificationServiceExtension {
// call notifications should stand out from normal messages, so we don't stack them
threadIdentifier = nil
- self.sendVoipPush(forEvent: event)
+
+ if let callInviteContent = MXCallInviteEventContent(fromJSON: event.content),
+ callInviteContent.lifetime > event.age,
+ (callInviteContent.lifetime - event.age) > UInt(NSE.Constants.timeNeededToSendVoIPPushes * MSEC_PER_SEC) {
+ self.sendVoipPush(forEvent: event)
+ } else {
+ NSLog("[NotificationService] notificationContent: Do not attempt to send a VoIP push, there is not enough time to process it.")
+ }
case .roomMessage, .roomEncrypted:
if isRoomMentionsOnly {
// A local notification will be displayed only for highlighted notification.
@@ -559,20 +587,43 @@ class NotificationService: UNNotificationServiceExtension {
return
}
- pushNotificationStore.lastCallInvite = event
+ if #available(iOS 13.0, *) {
+ if event.isEncrypted {
+ guard let clearEvent = event.clear else {
+ NSLog("[NotificationService] sendVoipPush: Do not send a VoIP push for undecrypted event, it'll cause a crash.")
+ return
+ }
+
+ // Add some original data on the clear event
+ clearEvent.eventId = event.eventId
+ clearEvent.originServerTs = event.originServerTs
+ clearEvent.sender = event.sender
+ clearEvent.roomId = event.roomId
+ pushNotificationStore.storeCallInvite(clearEvent)
+ } else {
+ pushNotificationStore.storeCallInvite(event)
+ }
+ }
ongoingVoIPPushRequests[event.eventId] = true
let appId = BuildSettings.pushKitAppId
- pushGatewayRestClient.notifyApp(withId: appId, pushToken: token, eventId: event.eventId, roomId: event.roomId, eventType: nil, sender: event.sender, success: { [weak self] (rejected) in
- NSLog("[NotificationService] sendVoipPush succeeded, rejected tokens: \(rejected)")
-
- guard let self = self else { return }
- self.ongoingVoIPPushRequests.removeValue(forKey: event.eventId)
-
- self.fallbackToBestAttemptContent(forEventId: event.eventId)
- }) { [weak self] (error) in
+ pushGatewayRestClient.notifyApp(withId: appId,
+ pushToken: token,
+ eventId: event.eventId,
+ roomId: event.roomId,
+ eventType: nil,
+ sender: event.sender,
+ timeout: NSE.Constants.voipPushRequestTimeout,
+ success: { [weak self] (rejected) in
+ NSLog("[NotificationService] sendVoipPush succeeded, rejected tokens: \(rejected)")
+
+ guard let self = self else { return }
+ self.ongoingVoIPPushRequests.removeValue(forKey: event.eventId)
+
+ self.fallbackToBestAttemptContent(forEventId: event.eventId)
+ }) { [weak self] (error) in
NSLog("[NotificationService] sendVoipPush failed with error: \(error)")
guard let self = self else { return }
diff --git a/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorBridgePresenter.swift b/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorBridgePresenter.swift
index 65bfb1406..bb7f824e4 100644
--- a/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorBridgePresenter.swift
+++ b/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorBridgePresenter.swift
@@ -54,7 +54,9 @@ final class FlowTemplateCoordinatorBridgePresenter: NSObject {
func present(from viewController: UIViewController, animated: Bool) {
let flowTemplateCoordinator = FlowTemplateCoordinator(session: self.session)
flowTemplateCoordinator.delegate = self
- viewController.present(flowTemplateCoordinator.toPresentable(), animated: animated, completion: nil)
+ let presentable = flowTemplateCoordinator.toPresentable()
+ presentable.presentationController?.delegate = self
+ viewController.present(presentable, animated: animated, completion: nil)
flowTemplateCoordinator.start()
self.coordinator = flowTemplateCoordinator
@@ -80,3 +82,13 @@ extension FlowTemplateCoordinatorBridgePresenter: FlowTemplateCoordinatorDelegat
self.delegate?.flowTemplateCoordinatorBridgePresenterDelegateDidComplete(self)
}
}
+
+// MARK: - UIAdaptivePresentationControllerDelegate
+
+extension FlowTemplateCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate {
+
+ func flowTemplateCoordinatorDidComplete(_ presentationController: UIPresentationController) {
+ self.delegate?.flowTemplateCoordinatorBridgePresenterDelegateDidComplete(self)
+ }
+
+}