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) + } + +}